import { CommonModule } from '@angular/common'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, Self, ViewChild, ViewEncapsulation, ChangeDetectorRef, HostListener, AfterContentInit, } from '@angular/core'; import { BehaviorSubject, catchError, debounceTime, distinctUntilChanged, filter, Observable, of, Subject, switchMap, takeUntil, tap, } from 'rxjs'; import { UntypedFormGroup, NgControl, ControlValueAccessor, FormsModule, } from '@angular/forms'; // Config import { ICaInput } from '../ca-input-test/config'; // Components import { CaAppTooltipV2Component } from '../ca-app-tooltip-v2/ca-app-tooltip-v2.component'; import { CaInputDropdownTestComponent } from '../ca-input-dropdown-test/ca-input-dropdown-test.component'; // Modules import { AngularSvgIconModule } from 'angular-svg-icon'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; // enums import { InputAddressCommandsStringEnum } from './enums/input-address-commands-string.enum'; import { InputAddressStopTypesStringEnum } from './enums/input-address-stop-types-string.enum'; import { InputAddressTypeStringEnum } from './enums/input-address-type-string.enum'; import { InputAddressLayersStringEnum } from './enums/input-address-layers-string.enum'; import { eInputBasicString } from './enums/input-address-basic-string.enum'; // models import { AddressData } from './models/address-data.model'; import { AddressList } from './models/address-list.model'; import { CommandsHandler } from './models/commands-handler.model'; import { AutocompleteSearchLayer } from '../../models/autocomplete-search-layer.model'; import { InputAddressCommandsString } from './models/input-address-commands-string.model'; import { SentAddressData } from './models/sent-address-data.model'; import { AddressListResponse } from '../../models/address-list-response.model'; // validators import { addressNameWithDotValidator } from './validators/ca-input-address-value.validations'; @Component({ selector: 'ca-input-address-dropdown-test', templateUrl: './ca-input-address-dropdown-test.component.html', styleUrls: [ './ca-input-address-dropdown-test.component.scss', '../ca-input/ca-input.component.scss', ], encapsulation: ViewEncapsulation.None, imports: [ // Modules CommonModule, FormsModule, NgbModule, ReactiveFormsModule, AngularSvgIconModule, HttpClientModule, // Components CaInputDropdownTestComponent, CaAppTooltipV2Component, ] }) export class CaInputAddressDropdownTestComponent implements OnInit, ControlValueAccessor, OnDestroy, AfterContentInit { @ViewChild('inputDropdown', { static: true }) inputDropdown!: CaInputDropdownTestComponent; @Input() set placeholderType(value: string) { this.checkSearchLayers(value); } @Input() public set receivedAddressData(value: AddressData | null) { if (value) { this.currentAddressData = { address: value.address!, valid: value.address && value.longLat ? true : false, longLat: value.longLat, }; this.onChange({ ...value.address, latitude: value.longLat?.latitude, longitude: value.longLat?.longitude, }); this.selectedAddress.emit(this.currentAddressData!); } } @Input() public set receivedAddressList(value: AddressListResponse | null) { this._receivedAddressList = value; if (value?.addresses) { this.addresList = value.addresses.map( (item: string, indx: number) => { return { address: item, id: indx, }; } ); } } @Output() public sentAddressValue: EventEmitter = new EventEmitter(); @Input() public inputConfig!: ICaInput; @Input() public commandHandler!: CommandsHandler; @Input() public isRouting: boolean = false; @Input() public closedBorder: boolean = false; @Input() public incorrectValue!: boolean; @Input() public hideEmptyLoaded: boolean = false; public addresList!: AddressList[]; @Output() selectedAddress: EventEmitter = new EventEmitter(); @Output() sentAddressData: EventEmitter = new EventEmitter(); @Output() closeDropdown: EventEmitter = new EventEmitter(); @Output() commandEvent: EventEmitter = new EventEmitter< AddressData | {} >(); @Output() changeFlag: EventEmitter = new EventEmitter(); @Output() incorrectEvent: EventEmitter = new EventEmitter(); @HostListener('document:keydown', ['$event']) handleKeyboardEvent(event: KeyboardEvent) { const key = event.key; if ( this.inputConfig.name == InputAddressTypeStringEnum.ROUTING_ADDRESS ) { if (key === InputAddressCommandsStringEnum.ENTER) { if (this.currentAddressData) { this.onCommands( event, InputAddressCommandsStringEnum.CONFIRM ); } } else if (key === InputAddressCommandsStringEnum.ESCAPE) { this.clearInput(event); } } } //Address data private searchLayers!: AutocompleteSearchLayer[]; public currentAddressData: AddressData | null = null; //Confg public addressExpanded: boolean = false; public chosenFromDropdown: boolean = false; private allowValidation: boolean = false; public stopType: string = InputAddressStopTypesStringEnum.EMPTY; private requestSent: boolean = false; public _receivedAddressList: AddressListResponse | null = null; private destroy$ = new Subject(); public addressForm!: UntypedFormGroup; constructor( @Self() public superControl: NgControl, private ref: ChangeDetectorRef ) { this.superControl.valueAccessor = this; } ngAfterContentInit(): void {} writeValue(address: any): void { if (address) { this.inputDropdown?.setDropdownValue({ option: address, label: 'address', }); this.onChange(address); } } public registerOnChange(fn: any): void { this.onChange = fn; } public onChange(_: any): void {} public registerOnTouched(_: any): void {} ngOnInit(): void { this.initChangesListener(); this.attachAddressStreetValidation(); } public attachAddressStreetValidation() { if (this.searchLayers[0] === InputAddressLayersStringEnum.ADDRESS) { const currentValidators = this.superControl.validator ? [this.superControl.validator] : []; this.superControl?.control?.setValidators([ ...currentValidators, addressNameWithDotValidator(), ]); this.superControl?.control?.updateValueAndValidity(); } } private observableInputSearch = new BehaviorSubject(''); public observableInputSearch$: Observable = this.observableInputSearch.asObservable(); public handleSearchInput(text: string): void { this.observableInputSearch.next(text); } public initChangesListener(): void { this.observableInputSearch$ .pipe( distinctUntilChanged(), takeUntil(this.destroy$), tap((term) => { this.inputConfig = { ...this.inputConfig, loadingSpinner: { ...this.inputConfig.loadingSpinner, isLoading: true, size: eInputBasicString.SMALL, color: eInputBasicString.WHITE, }, }; if (!term) { this.inputConfig = { ...this.inputConfig, loadingSpinner: { ...this.inputConfig.loadingSpinner, isLoading: false, }, }; this.addresList = []; } else if ( term !== this.currentAddressData?.address.address && this.inputConfig.name === InputAddressTypeStringEnum.ROUTING_ADDRESS ) { this.currentAddressData = null; } if ( this.inputConfig.name !== InputAddressTypeStringEnum.ROUTING_ADDRESS && this.allowValidation && this.inputDropdown.inputRef.isFocusInput ) { this.requestSent = false; const addressData = { address: {}, valid: false, longLat: {}, }; this.selectedAddress.emit(addressData); } this.allowValidation = true; }), filter((term: string) => { return term?.length >= 3; }), debounceTime(500), switchMap((query: string) => { const params = { query: query, searchLayers: this.searchLayers, closedBorder: this.closedBorder, }; this.sentAddressData.next(params); return of(this.receivedAddressList).pipe( catchError(() => of([])) ); }) ) .subscribe(() => { this.inputConfig = { ...this.inputConfig, loadingSpinner: { ...this.inputConfig.loadingSpinner, isLoading: false, }, }; this.ref.detectChanges(); }); } get getSuperControl() { return this.superControl.control as FormControl; } public onCloseDropdown(event: boolean): void { this.closeDropdown.emit(event); } public getAddressData(address: string): void { this.requestSent = true; this.sentAddressValue.emit(address); } public onSelectDropdown(event: AddressList | null): void { if (event?.address) { if (this.searchLayers[0] === InputAddressLayersStringEnum.ADDRESS) { const isValid = this.checkAddressValidation(event.address); if (isValid) { this.getAddressData(event.address); this.onChange(event.address); } } else { this.getAddressData(event.address); this.onChange(event.address); } this.chosenFromDropdown = true; } else { this.onClearInputEvent(); this.currentAddressData = null; this.addresList = []; } this.inputDropdown?.popoverRef?.close(); } public onCommands( event: KeyboardEvent, type: InputAddressCommandsString ): void { event.preventDefault(); event.stopPropagation(); if ( (type === InputAddressCommandsStringEnum.CONFIRM && this.currentAddressData) || type === InputAddressCommandsStringEnum.CANCEL ) { this.currentAddressData!.type = type; this.commandEvent.emit(this.currentAddressData ?? {}); this.closeAddress(); this.clearInput(event); } } public addressExpand(): void { if (!this.addressExpanded) this.addressExpanded = true; } public closeAddress(): void { this.addressExpanded = false; } public clearInput(event: KeyboardEvent): void { this.currentAddressData = null; this.addresList = []; this.inputDropdown?.inputRef?.clearInput(event); this.chosenFromDropdown = false; } private checkSearchLayers(value: string): void { this.searchLayers = value === InputAddressTypeStringEnum.LONG_ADDRESS ? [InputAddressLayersStringEnum.ADDRESS] : value === InputAddressTypeStringEnum.SHORT_ADDRESS ? [InputAddressLayersStringEnum.LOCALITY] : []; } public changeStopType(): void { let flag = false; if (this.stopType === InputAddressStopTypesStringEnum.EMPTY) { this.stopType = InputAddressStopTypesStringEnum.LOADED; flag = true; } else { this.stopType = InputAddressStopTypesStringEnum.EMPTY; } this.changeFlag.emit(flag); if (!this.chosenFromDropdown) { this.inputDropdown?.inputRef?.inputElement.nativeElement.focus(); setTimeout(() => { this.inputDropdown.inputRef.isFocusInput = true; }, 500); } } public onIncorrectInput(event: boolean): void { this.incorrectEvent.emit(event); } public onClearInputEvent(): void { const addressData = { address: {}, valid: false, longLat: {}, }; this.selectedAddress.emit(addressData); } private checkAddressValidation(address: string): boolean { const streetNum = /\d.*\d/; return streetNum.test(address) ? true : false; } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } }