import { Component, ElementRef, EventEmitter, HostListener, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewChild } from "@angular/core"; import {AbstractControl, FormArray, FormControl, ValidatorFn} from "@angular/forms"; import {HelperFunctions} from "../../utils/HelperFunctions.class"; import {Observable, of, Subscription} from "rxjs"; import {MatSelect} from "@angular/material/select"; import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete"; import {map, startWith} from "rxjs/operators"; import {MatChipInputEvent} from "@angular/material/chips"; export interface Option { icon?: string, iconClass?: string, value: any, label: string } @Component({ selector: '[dashboard-input]', template: `
{{label + (required ? ' *' : '')}}
{{hint}}
{{placeholder}} {{option.label}}
{{getLabel(formControl.value)}}
{{placeholder}}
{{option.label}}
{{getLabel(chip.value)}}
{{placeholder}}
{{option.label}}
{{formControl.errors.error}} Please provide a valid URL (e.g. https://example.com) Note: Prefer urls like "https://example.com/my-secure-image.png" instead of "http://example.com/my-image.png". Browsers may not load non secure content.
{{label}} `, styleUrls: ['input.component.css'] }) export class InputComponent implements OnInit, OnDestroy, OnChanges { /** Basic information */ @Input('formInput') formControl: AbstractControl; @Input('type') type: 'text' | 'URL' | 'logoURL' | 'autocomplete' | 'textarea' | 'select' | 'checkbox' | 'chips' = 'text'; @Input('label') label: string; /** Text */ @ViewChild('input') input: ElementRef; /** Textarea options */ @Input('rows') rows: number = 3; /** Select | chips available options */ @Input('options') options: Option[] = []; @Input('hint') hint = null; @Input('placeholder') placeholder = ''; @Input() inputClass: string = 'input-box'; /** Extra element Right or Left of the input */ @Input() extraLeft: boolean = true; @Input() gridSmall: boolean = false; @Input() hideControl: boolean = false; @Input() flex: 'middle' | 'top' | 'bottom' = 'middle'; /** Icon Right or Left on the input */ @Input() icon: string = null; @Input() iconLeft: boolean = false; /** Chip options */ @Input() removable: boolean = true; @Input() addExtraChips: boolean = false; @Input() smallChip: boolean = false; @Input() panelWidth: number = 300; @Input() panelClass: string = null; @Input() showOptionsOnEmpty: boolean = true; @Input() validators: ValidatorFn[]; @Output() focusEmitter: EventEmitter = new EventEmitter(); /** LogoUrl information */ public secure: boolean = true; /** Internal basic information */ public required: boolean = false; private initValue: any; /** Chips && Autocomplete*/ public filteredOptions: Observable; public searchControl: FormControl; private subscriptions: any[] = []; @ViewChild('select') select: MatSelect; @ViewChild('searchInput') searchInput: ElementRef; focused: boolean = false; constructor(private elementRef: ElementRef) { } @HostListener('document:click', ['$event']) clickOut(event) { this.focused = !!this.elementRef.nativeElement.contains(event.target); this.focusEmitter.emit(this.focused); } ngOnInit(): void { this.reset(); } ngOnChanges(changes: SimpleChanges) { if (changes.formControl) { this.reset(); } } get formAsArray(): FormArray { return (this.formControl); } reset() { this.secure = true; this.unsubscribe(); this.initValue = HelperFunctions.copy(this.formControl.value); if(this.type === 'logoURL') { this.secure = (!this.initValue || this.initValue.includes('https://')); } if (this.type === 'chips' || this.type === 'autocomplete') { if(this.options) { this.filteredOptions = of(this.options); this.searchControl = new FormControl('', this.validators); this.subscriptions.push(this.searchControl.valueChanges.subscribe(value => { setTimeout(() => { this.searchInput.nativeElement.focus(); this.searchInput.nativeElement.value = value; },0); })); this.filteredOptions = this.searchControl.valueChanges.pipe(startWith(''), map(option => this.filter(option))); } } if (this.formControl && this.formControl.validator) { let validator = this.formControl.validator({} as AbstractControl); this.required = (validator && validator.required); } this.subscriptions.push(this.formControl.valueChanges.subscribe(value => { value = (value === '') ? null : value; if(this.type === 'logoURL') { this.secure = (!value || value.includes('https://')); } if (this.initValue === value || (this.initValue === '' && value === null)) { this.formControl.markAsPristine(); } if(this.searchControl) { this.searchControl.setValue(null); } })); if (!this.formControl.value) { this.formControl.setValue((this.type === "checkbox")?false:''); } } unsubscribe() { this.subscriptions.forEach(subscription => { if (subscription instanceof Subscription) { subscription.unsubscribe(); } }); } openSelect() { if (this.select) { this.select.open(); } } ngOnDestroy(): void { this.unsubscribe(); } stopPropagation() { event.stopPropagation(); } removed(index: number) { this.formAsArray.removeAt(index); this.formAsArray.markAsDirty(); this.searchControl.setValue(''); this.stopPropagation(); } selected(event: MatAutocompleteSelectedEvent): void { this.formAsArray.push(new FormControl(event.option.value)); this.formAsArray.markAsDirty(); this.searchControl.setValue(''); this.stopPropagation(); } private filter(value: string): Option[] { let options = this.options; if(this.type === "chips") { options = options.filter(option => !this.formAsArray.value.find(value => HelperFunctions.equals(option.value, value))); } if ((!value || value.length == 0)) { return (this.showOptionsOnEmpty)?options:[]; } const filterValue = value.toString().toLowerCase(); return options.filter(option => option.label.toLowerCase().indexOf(filterValue) != -1); } add(event: MatChipInputEvent) { if (this.addExtraChips && event.value && this.searchControl.valid) { this.stopPropagation(); this.formAsArray.push(new FormControl(event.value, this.validators)); this.formAsArray.markAsDirty(); this.searchControl.setValue(''); this.searchInput.nativeElement.value = ''; } } getLabel(value: any) { let option = this.options.find(option => HelperFunctions.equals(option.value, value)); return (option) ? option.label : value; } resetSearch(event: any) { event.stopPropagation(); this.searchControl.setValue(''); this.formControl.setValue(null); } }