You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
argos/dmp-frontend/src/app/library/auto-complete/single/single-auto-complete.compon...

240 lines
7.2 KiB
TypeScript

import { FocusMonitor } from '@angular/cdk/a11y';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Optional, Output, Self } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatFormFieldControl } from '@angular/material/form-field';
import { Observable, of as observableOf, Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, mergeMap, startWith, tap } from 'rxjs/operators';
import { AutoCompleteGroup } from '../auto-complete-group';
import { SingleAutoCompleteConfiguration } from './single-auto-complete-configuration';
import { switchMap } from 'rxjs/internal/operators/switchMap';
@Component({
selector: 'app-single-auto-complete',
templateUrl: './single-auto-complete.component.html',
styleUrls: ['./single-auto-complete.component.scss'],
providers: [{ provide: MatFormFieldControl, useExisting: SingleAutoCompleteComponent }],
})
export class SingleAutoCompleteComponent implements OnInit, MatFormFieldControl<string>, ControlValueAccessor, OnDestroy {
static nextId = 0;
@Input() configuration: SingleAutoCompleteConfiguration;
// Selected Option Event
@Output() optionSelected: EventEmitter<any> = new EventEmitter();
id = `single-autocomplete-${SingleAutoCompleteComponent.nextId++}`;
stateChanges = new Subject<void>();
focused = false;
errorState = false;
controlType = 'single-autocomplete';
describedBy = '';
_inputValue: string;
_inputSubject = new Subject<string>();
loading = false;
_items: Observable<any[]>;
_groupedItems: Observable<AutoCompleteGroup[]>;
private requestDelay = 200; //ms
private minFilteringChars = 1;
private loadDataOnStart = true;
separatorKeysCodes: number[] = [ENTER, COMMA];
get empty() {
return !this._inputValue || this._inputValue.length === 0;
}
get shouldLabelFloat() { return this.focused || !this.empty; }
@Input()
get placeholder() { return this._placeholder; }
set placeholder(placeholder) {
this._placeholder = placeholder;
this.stateChanges.next();
}
private _placeholder: string;
@Input()
get required() { return this._required; }
set required(req) {
this._required = !!(req);
this.stateChanges.next();
}
private _required = false;
@Input()
get disabled() { return this._disabled; }
set disabled(dis) {
this._disabled = !!(dis);
this.stateChanges.next();
}
private _disabled = false;
@Input()
get value(): any | null {
return this._selectedValue;
}
set value(value: any | null) {
this._selectedValue = value;
this._inputValue = (value && value != "") ? " " : null;
this.stateChanges.next();
}
private _selectedValue;
constructor(
private fm: FocusMonitor,
private elRef: ElementRef,
@Optional() @Self() public ngControl: NgControl) {
fm.monitor(elRef.nativeElement, true).subscribe((origin) => {
this.focused = !!origin;
this.stateChanges.next();
});
if (this.ngControl != null) {
// Setting the value accessor directly (instead of using
// the providers) to avoid running into a circular import.
this.ngControl.valueAccessor = this;
}
}
ngOnInit() {
}
filter(query: string): Observable<any[]> {
// If loadDataOnStart is enabled and query is empty we return the initial items.
if (this.isNullOrEmpty(query) && this.loadDataOnStart) {
return this.configuration.initialItems(this.configuration.extraData) || observableOf([]);
} else if (query && query.length >= this.minFilteringChars) {
if (this.configuration.filterFn) {
return this.configuration.filterFn(query, this.configuration.extraData);
} else {
return this.configuration.initialItems(this.configuration.extraData) || observableOf([]);
}
} else {
return observableOf([]);
}
}
isNullOrEmpty(query: string): boolean {
return typeof query !== 'string' || query === null || query.length === 0;
}
_displayFn(item: any): string {
if (this.configuration.displayFn && item) { return this.configuration.displayFn(item); }
return item;
}
_titleFn(item: any): string {
if (this.configuration.titleFn && item) { return this.configuration.titleFn(item); }
return item;
}
_subtitleFn(item: any): string {
if (this.configuration.subtitleFn && item) { return this.configuration.subtitleFn(item); }
return null;
}
_requestDelay(): number {
return this.configuration.requestDelay || this.requestDelay;
}
_minFilteringChars(): number {
return this.configuration.minFilteringChars || this.minFilteringChars;
}
_loadDataOnStart(): boolean {
return this.configuration.loadDataOnStart || this.loadDataOnStart;
}
_optionSelected(event: MatAutocompleteSelectedEvent) {
this._setValue(this.configuration.valueAssign ? this.configuration.valueAssign(event.option.value) : event.option.value);
//this._inputValue = " ";
this.stateChanges.next();
this.optionSelected.emit(event.option.value);
if (this.configuration.removeAfterSelection) {
this.clearAutocomplete()
}
}
private clearAutocomplete() {
this._setValue(null);
}
private _setValue(value: any) {
this.value = value;
this.pushChanges(this.value);
}
_onInputFocus() {
// We set the items observable on focus to avoid the request being executed on component load.
if (!this._items) {
this._items = this._inputSubject.pipe(
startWith(null),
debounceTime(this.requestDelay),
distinctUntilChanged(),
tap(() => { this.loading = true; }),
switchMap(query => {
// If its a valid object, a selection just made and the object is set as the value of the form control. That means we should fire an extra request to the server.
if (this._isValidObject(query)) { return observableOf([]); }
// Since the object is changed we need to clear any existing selections, except for the first time.
if (query !== null) { this.pushChanges(null); }
return this.filter(query);
}),
tap(() => { this.loading = false; }));
if (this.configuration.groupingFn) { this._groupedItems = this._items.pipe(map(items => this.configuration.groupingFn(items))); }
}
}
_inputValueChange(value: string) {
//this._inputValue = value;
this._inputSubject.next(value);
this.stateChanges.next();
}
_isValidObject(value: any): boolean {
try {
if (!value) { return false; }
if (typeof value !== 'object') { JSON.parse(value); }
} catch (e) {
return false;
}
return true;
}
onChange = (_: any) => { };
onTouched = () => { };
writeValue(value: any): void { this.value = value || ''; }
pushChanges(value: any) { this.onChange(value); }
registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; }
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; }
setDescribedByIds(ids: string[]) {
this.describedBy = ids.join(' ');
}
onContainerClick(event: MouseEvent) {
if ((event.target as Element).tagName.toLowerCase() !== 'input') {
this.elRef.nativeElement.querySelector('input').focus();
}
}
chipRemove(): void {
this._setValue(null);
}
autoCompleteDisplayFn() {
return (val) => "";
}
ngOnDestroy() {
this.stateChanges.complete();
this.fm.stopMonitoring(this.elRef.nativeElement);
}
}