autocomplete changes
This commit is contained in:
parent
8fb3556acb
commit
4542189a26
|
@ -68,7 +68,7 @@ public class FieldSearchBuilder extends BaseBuilder<Field, Map<String, String>>
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ReferenceTypeField> getPropertiesFields(ReferenceType referenceType){
|
private List<ReferenceTypeField> getPropertiesFields(ReferenceType referenceType){
|
||||||
switch (referenceType){
|
switch (referenceType) {
|
||||||
case Taxonomies: return properties.getTaxonomy().get("fields");
|
case Taxonomies: return properties.getTaxonomy().get("fields");
|
||||||
case Licenses: return properties.getLicence().get("fields");
|
case Licenses: return properties.getLicence().get("fields");
|
||||||
case Publications: return properties.getPublication().get("fields");
|
case Publications: return properties.getPublication().get("fields");
|
||||||
|
@ -80,6 +80,7 @@ public class FieldSearchBuilder extends BaseBuilder<Field, Map<String, String>>
|
||||||
case Organizations: return properties.getOrganisation().get("fields");
|
case Organizations: return properties.getOrganisation().get("fields");
|
||||||
case Datasets: return properties.getDataset().get("fields");
|
case Datasets: return properties.getDataset().get("fields");
|
||||||
case Funder: return properties.getFunder().get("fields");
|
case Funder: return properties.getFunder().get("fields");
|
||||||
|
case Grants: return properties.getGrant().get("fields");
|
||||||
case Project: return properties.getProject().get("fields");
|
case Project: return properties.getProject().get("fields");
|
||||||
case Researcher: return properties.getResearcher().get("fields");
|
case Researcher: return properties.getResearcher().get("fields");
|
||||||
default: return null;
|
default: return null;
|
||||||
|
|
|
@ -138,6 +138,7 @@ export class ReferenceService {
|
||||||
titleFn: (item: Reference) => item.label,
|
titleFn: (item: Reference) => item.label,
|
||||||
subtitleFn: (item: Reference) => item?.sourceType === ReferenceSourceType.External ? this.language.instant('REFERENCE-FIELD-COMPONENT.EXTERNAL-SOURCE') + ': ' + item.source : this.language.instant('REFERENCE-FIELD-COMPONENT.INTERNAL-SOURCE'),
|
subtitleFn: (item: Reference) => item?.sourceType === ReferenceSourceType.External ? this.language.instant('REFERENCE-FIELD-COMPONENT.EXTERNAL-SOURCE') + ': ' + item.source : this.language.instant('REFERENCE-FIELD-COMPONENT.INTERNAL-SOURCE'),
|
||||||
valueAssign: (item: Reference) => item,
|
valueAssign: (item: Reference) => item,
|
||||||
|
uniqueAssign: (item: Reference) => item.source + '_' + item.reference,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ export interface MultipleAutoCompleteConfiguration {
|
||||||
// Static or initial items.
|
// Static or initial items.
|
||||||
initialItems?: (excludedItems: any[], data?: any) => Observable<any[]>;
|
initialItems?: (excludedItems: any[], data?: any) => Observable<any[]>;
|
||||||
// Data retrieval function
|
// Data retrieval function
|
||||||
filterFn?: (searchQuery: string, excludedItems: any[]) => Observable<any[]>;
|
filterFn?: (searchQuery: string, excludedItems: any[], extraData?: any) => Observable<any[]>;
|
||||||
// Get selected items. Used when valueAssign is used so the full object can be retrieved for presentation.
|
// Get selected items. Used when valueAssign is used so the full object can be retrieved for presentation.
|
||||||
getSelectedItems?: (selectedItems: any[]) => Observable<any[]>;
|
getSelectedItems?: (selectedItems: any[]) => Observable<any[]>;
|
||||||
// Property formating for input
|
// Property formating for input
|
||||||
|
@ -28,6 +28,8 @@ export interface MultipleAutoCompleteConfiguration {
|
||||||
extraData?: any;
|
extraData?: any;
|
||||||
// Callback to intercept value assignment based on item selection
|
// Callback to intercept value assignment based on item selection
|
||||||
valueAssign?: (selectedItem: any) => any;
|
valueAssign?: (selectedItem: any) => any;
|
||||||
|
// Callback to intercept value assignment based on item selection
|
||||||
|
uniqueAssign?: (selectedItem: any) => any;
|
||||||
// Property formating template
|
// Property formating template
|
||||||
optionTemplate?: TemplateRef<any>;
|
optionTemplate?: TemplateRef<any>;
|
||||||
// Property formatting component
|
// Property formatting component
|
||||||
|
|
|
@ -1,30 +1,11 @@
|
||||||
<div class="row multiple-auto-complete" ngForm>
|
<div class="row multiple-auto-complete" ng-form>
|
||||||
<!-- <mat-chip-grid #chipList ngDefaultControl style="width: 100% !important;">
|
|
||||||
<ng-container *ngIf="value as values">
|
|
||||||
<mat-chip *ngFor="let value of values" [disabled]="disabled" [selectable]="selectable" [removable]="removable" [ngClass]="computeClass(value)">
|
|
||||||
<ng-container *ngIf="_selectedItems.get(stringify(value)) as selectedItem">
|
|
||||||
<ng-template #cellTemplate *ngIf="_selectedValueTemplate(selectedItem)" [ngTemplateOutlet]="_selectedValueTemplate(selectedItem)" [ngTemplateOutletContext]="{ item: selectedItem }"></ng-template>
|
|
||||||
<div class="row">
|
|
||||||
<div *ngIf="!_selectedValueTemplate(selectedItem)">{{_displayFn(selectedItem)}}</div>
|
|
||||||
<mat-icon matChipRemove *ngIf="!disabled && removable" (click)="_removeSelectedItem(_selectedItems.get(stringify(value)), $event)">cancel</mat-icon>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</mat-chip>
|
|
||||||
</ng-container>
|
|
||||||
<input matInput #autocompleteInput class="col" style="width: calc(100% - 8px) !important;" [class.hide-placeholder]="hidePlaceholder" [name]="id" autocomplete="off" #autocompleteTrigger="matAutocompleteTrigger" [placeholder]="placeholder" [matAutocomplete]="autocomplete" [value]="inputValue" (keyup)="onKeyUp($event)" (keydown)="onKeyDown($event)" [disabled]="disabled" (focus)="_onInputFocus()" (blur)="onBlur($event)" [matChipInputFor]="chipList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes" (matChipInputTokenEnd)="_addItem($event)" [matAutocompleteConnectedTo]="origin">
|
|
||||||
<mat-icon *ngIf="!disabled" class="align-arrow-right" matSuffix>arrow_drop_down</mat-icon>
|
|
||||||
<div style="height: 0 !important; width: 100% !important; visibility: hidden !important;" matAutocompleteOrigin #origin="matAutocompleteOrigin"></div>
|
|
||||||
</mat-chip-grid> -->
|
|
||||||
|
|
||||||
<mat-chip-grid #chipList ngDefaultControl class="chip-list">
|
<mat-chip-grid #chipList ngDefaultControl class="chip-list">
|
||||||
<ng-container *ngIf="value as values;">
|
<ng-container *ngIf="value as values;">
|
||||||
<mat-chip-row *ngFor="let value of values" [disabled]="disabled" [selectable]="selectable" [removable]="removable" [ngClass]="computeClass(value)">
|
<mat-chip-row *ngFor="let value of values" [disabled]="disabled" [selectable]="selectable" [removable]="removable" [ngClass]="computeClass(value)">
|
||||||
<ng-container *ngIf="_selectedItems.get(stringify(value)) as selectedItem;">
|
<ng-container *ngIf="getSelectedItem(value) as selectedItem;">
|
||||||
|
|
||||||
<ng-template #cellTemplate *ngIf="_selectedValueTemplate(selectedItem)" [ngTemplateOutlet]="_selectedValueTemplate(selectedItem)" [ngTemplateOutletContext]="{
|
<ng-template #cellTemplate *ngIf="_selectedValueTemplate(selectedItem)" [ngTemplateOutlet]="_selectedValueTemplate(selectedItem)" [ngTemplateOutletContext]="{
|
||||||
item: selectedItem
|
item: selectedItem
|
||||||
}" />
|
}" />
|
||||||
|
|
||||||
<span *ngIf="!_selectedValueTemplate(selectedItem)" class="chip-text" [title]="_displayFn(selectedItem)">{{_displayFn(selectedItem)}}</span>
|
<span *ngIf="!_selectedValueTemplate(selectedItem)" class="chip-text" [title]="_displayFn(selectedItem)">{{_displayFn(selectedItem)}}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<button matChipRemove *ngIf="!disabled && removable" (click)="_removeSelectedItem(_selectedItems.get(stringify(value)), $event)">
|
<button matChipRemove *ngIf="!disabled && removable" (click)="_removeSelectedItem(_selectedItems.get(stringify(value)), $event)">
|
||||||
|
@ -33,17 +14,17 @@
|
||||||
</mat-chip-row>
|
</mat-chip-row>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</mat-chip-grid>
|
</mat-chip-grid>
|
||||||
<input matInput #autocompleteInput class="col" [name]="id" autocomplete="nope" #autocompleteTrigger="matAutocompleteTrigger" autocomplete="off" [placeholder]="placeholder" [matAutocomplete]="autocomplete" [value]="inputValue" (keyup)="onKeyUp($event)" [disabled]="disabled" (focus)="_onInputFocus()" (blur)="onBlur($event)" [matChipInputFor]="chipList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="autoSelectFirstOptionOnBlur" (matChipInputTokenEnd)="_addItem($event)" [matAutocompleteConnectedTo]="origin">
|
<input matInput #autocompleteInput class="col" [name]="id" autocomplete="nope" #autocompleteTrigger="matAutocompleteTrigger" autocomplete="off" [placeholder]="placeholder" [matAutocomplete]="autocomplete" [value]="inputValue" (keyup)="onKeyUp($event)" [disabled]="disabled" (focus)="_onInputFocus()" (blur)="onBlur($event)" [matChipInputFor]="chipList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="autoSelectFirstOptionOnBlur" (matChipInputTokenEnd)="_addItem($event)">
|
||||||
<mat-icon *ngIf="!disabled" class="align-arrow-right" matSuffix>arrow_drop_down</mat-icon>
|
<mat-icon *ngIf="!disabled" class="align-arrow-right" matSuffix>arrow_drop_down</mat-icon>
|
||||||
<div style="height: 0 !important; width: 100% !important; visibility: hidden !important;" matAutocompleteOrigin #origin="matAutocompleteOrigin"></div>
|
|
||||||
|
|
||||||
|
|
||||||
<mat-autocomplete #autocomplete="matAutocomplete" [displayWith]="_displayFn.bind(this)" (optionSelected)="_optionSelected($event)">
|
<mat-autocomplete #autocomplete="matAutocomplete" [displayWith]="_displayFn.bind(this)" (optionSelected)="_optionSelected($event)">
|
||||||
<span *ngIf="_groupedItems">
|
<span *ngIf="_groupedItems">
|
||||||
<mat-optgroup *ngFor="let group of _groupedItems | async" [label]="group.title">
|
<mat-optgroup *ngFor="let group of _groupedItems | async" [label]="group.title">
|
||||||
<mat-option *ngFor="let item of group.items" [value]="item" (mousedown)="selected = true;" (mouseup)="selected = false;" [class.two-line-mat-option]="_subtitleFn(item) && !_optionTemplate(item)">
|
<mat-option *ngFor="let item of group.items" [value]="item" class="option" [class.two-line-mat-option]="_subtitleFn(item) && !_optionTemplate(item) && !_optionComponent(item)">
|
||||||
<!-- <img style="vertical-align:middle;" aria-hidden src="{{state.flag}}" height="25" /> -->
|
<!-- <img style="vertical-align:middle;" aria-hidden src="{{state.flag}}" height="25" /> -->
|
||||||
<ng-template #cellTemplate *ngIf="_optionTemplate(item)" [ngTemplateOutlet]="_optionTemplate(item)" [ngTemplateOutletContext]="{
|
<ng-container *ngIf="_optionComponent(item)">
|
||||||
|
<ng-container *ngComponentOutlet="_optionComponent(item); inputs: { item };" />
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #cellTemplate *ngIf="_optionTemplate(item) && !_optionComponent(item)" [ngTemplateOutlet]="_optionTemplate(item)" [ngTemplateOutletContext]="{
|
||||||
item: item
|
item: item
|
||||||
}"></ng-template>
|
}"></ng-template>
|
||||||
<div *ngIf="!_optionTemplate(item)" class="d-flex">
|
<div *ngIf="!_optionTemplate(item)" class="d-flex">
|
||||||
|
@ -90,38 +71,6 @@
|
||||||
<ng-template #loading>
|
<ng-template #loading>
|
||||||
<mat-option disabled="true">loading...</mat-option>
|
<mat-option disabled="true">loading...</mat-option>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
|
||||||
<!-- <ng-container *ngIf="_items | async as autocompleteItems; else loading">
|
|
||||||
<ng-container *ngIf="autocompleteItems.length">
|
|
||||||
<mat-option *ngFor="let item of autocompleteItems" [value]="item" (mousedown)="selected = true;" (mouseup)="selected = false;" [class.two-line-mat-option]="_subtitleFn(item) && !_optionTemplate(item)">
|
|
||||||
<ng-template #cellTemplate *ngIf="_optionTemplate(item)" [ngTemplateOutlet]="_optionTemplate(item)" [ngTemplateOutletContext]="{
|
|
||||||
item: item
|
|
||||||
}"></ng-template>
|
|
||||||
<div *ngIf="!_optionTemplate(item)" class="d-flex">
|
|
||||||
<div class="title-subtitle-fn">
|
|
||||||
<div class="title-fn">
|
|
||||||
<div class="title-fn-inner">
|
|
||||||
<span *ngIf="!_optionTemplate(item)">{{_titleFn(item)}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="_subtitleFn(item)" class="subtitle-fn">
|
|
||||||
<div class="subtitle-fn-inner">
|
|
||||||
<small [innerHTML]="_subtitleFn(item)"></small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span *ngIf="popupItemActionIcon" class="option-icon" (click)="_optionActionClick(item, $event)"><mat-icon>{{popupItemActionIcon}}</mat-icon></span>
|
|
||||||
</div>
|
|
||||||
</mat-option>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!autocompleteItems.length && showNoResultsLabel && (queryValue == inputValue)">
|
|
||||||
<mat-option disabled="true">No results found!</mat-option>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
<ng-template #loading>
|
|
||||||
<mat-option disabled="true">loading...</mat-option>
|
|
||||||
</ng-template> -->
|
|
||||||
</span>
|
</span>
|
||||||
</mat-autocomplete>
|
</mat-autocomplete>
|
||||||
</div>
|
</div>
|
|
@ -28,8 +28,8 @@ import {
|
||||||
} from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration';
|
} from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration';
|
||||||
import { BaseComponent } from '@common/base/base.component';
|
import { BaseComponent } from '@common/base/base.component';
|
||||||
import { isNullOrUndefined } from '@swimlane/ngx-datatable';
|
import { isNullOrUndefined } from '@swimlane/ngx-datatable';
|
||||||
import { BehaviorSubject, Observable, Subject, Subscription, combineLatest, of as observableOf } from 'rxjs';
|
import { BehaviorSubject, Observable, Subject, Subscription, combineLatest, interval, of as observableOf, of } from 'rxjs';
|
||||||
import { debounceTime, distinctUntilChanged, map, startWith, switchMap, takeUntil, tap } from 'rxjs/operators';
|
import { catchError, debounceTime, delayWhen, distinctUntilChanged, map, mergeMap, startWith, switchMap, take, takeUntil, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
export class CustomComponentBase extends BaseComponent {
|
export class CustomComponentBase extends BaseComponent {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -84,7 +84,7 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp
|
||||||
|
|
||||||
valueOnBlur = new BehaviorSubject<any>(null);
|
valueOnBlur = new BehaviorSubject<any>(null);
|
||||||
onSelectAutoCompleteValue = new BehaviorSubject<any>(null);
|
onSelectAutoCompleteValue = new BehaviorSubject<any>(null);
|
||||||
valueAssignSubscription: Subscription;
|
// valueAssignSubscription: Subscription;
|
||||||
|
|
||||||
queryValue: string = "";
|
queryValue: string = "";
|
||||||
|
|
||||||
|
@ -170,9 +170,23 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp
|
||||||
) {
|
) {
|
||||||
super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl, new Subject<void>());
|
super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl, new Subject<void>());
|
||||||
|
|
||||||
fm.monitor(elRef.nativeElement, true).pipe(takeUntil(this._destroyed)).subscribe((origin) => {
|
fm.monitor(elRef.nativeElement, true)
|
||||||
|
.pipe(
|
||||||
|
distinctUntilChanged(),
|
||||||
|
switchMap(
|
||||||
|
(origin) => of(origin).pipe(
|
||||||
|
delayWhen((origin) => interval(!!origin ? 0 : 200).pipe(take(1))), // delay focusout (user may have made a selection) then close options if open
|
||||||
|
)
|
||||||
|
),
|
||||||
|
takeUntil(this._destroyed)
|
||||||
|
)
|
||||||
|
.subscribe((origin) => {
|
||||||
this.focused = !!origin;
|
this.focused = !!origin;
|
||||||
this.stateChanges.next();
|
this.stateChanges.next();
|
||||||
|
|
||||||
|
if (!this.focused) {
|
||||||
|
this.autocompleteTrigger?.closePanel();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.ngControl != null) {
|
if (this.ngControl != null) {
|
||||||
|
@ -182,36 +196,7 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() { }
|
||||||
this.valueAssignSubscription = combineLatest(this.valueOnBlur.asObservable(), this.onSelectAutoCompleteValue.asObservable())
|
|
||||||
.pipe(debounceTime(100))
|
|
||||||
.subscribe(latest => {
|
|
||||||
const fromBlur = latest[0];
|
|
||||||
const fromAutoComplete = latest[1];
|
|
||||||
|
|
||||||
if (isNullOrUndefined(fromBlur) && isNullOrUndefined(fromAutoComplete)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//higher precedence
|
|
||||||
if (!isNullOrUndefined(fromAutoComplete)) {
|
|
||||||
this.optionSelectedInternal(fromAutoComplete);
|
|
||||||
|
|
||||||
// consumed and flush
|
|
||||||
this.onSelectAutoCompleteValue.next(null);
|
|
||||||
this.valueOnBlur.next(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNullOrUndefined(fromBlur)) {
|
|
||||||
this.optionSelectedInternal(fromBlur);
|
|
||||||
|
|
||||||
// consumed and flush
|
|
||||||
this.onSelectAutoCompleteValue.next(null);
|
|
||||||
this.valueOnBlur.next(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngDoCheck(): void {
|
ngDoCheck(): void {
|
||||||
if (this.ngControl) {
|
if (this.ngControl) {
|
||||||
|
@ -220,37 +205,50 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes['configuration'] && changes['configuration'].isFirstChange) {
|
|
||||||
this.getSelectedItems(this.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private gettingSelectedItem$ = new Subject<void>();
|
||||||
getSelectedItems(value: any) {
|
getSelectedItems(value: any) {
|
||||||
if (value != null && Array.isArray(value) && this.configuration) {
|
if (value != null && Array.isArray(value) && this.configuration) {
|
||||||
const newSelections = value.filter(x => !this._selectedItems.has(this.stringify(x)));
|
const newSelections = value.filter(x => !this._selectedItems.has(JSON.stringify(x)));
|
||||||
if (newSelections.length > 0 && this.configuration.getSelectedItems != null) {
|
if (newSelections.length > 0 && this.configuration.getSelectedItems != null) {
|
||||||
this.configuration.getSelectedItems(newSelections).pipe(takeUntil(this._destroyed)).subscribe(x => {
|
this.gettingSelectedItem$.next();
|
||||||
|
this.configuration.getSelectedItems(newSelections).pipe(takeUntil(this._destroyed), takeUntil(this.gettingSelectedItem$)).subscribe(x => {
|
||||||
|
|
||||||
x.forEach(element => {
|
x.forEach(element => {
|
||||||
this._selectedItems.set(this.stringify(this.configuration.valueAssign != null ? this.configuration.valueAssign(element) : element), element);
|
this._selectedItems.set(JSON.stringify(
|
||||||
|
this.configuration.uniqueAssign != null ? this.configuration.uniqueAssign(element) :
|
||||||
|
this.configuration.valueAssign != null ? this.configuration.valueAssign(element) : element
|
||||||
|
), element);
|
||||||
});
|
});
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
newSelections.forEach(element => {
|
newSelections.forEach(element => {
|
||||||
this._selectedItems.set(this.stringify(this.configuration.valueAssign != null ? this.configuration.valueAssign(element) : element), element);
|
this._selectedItems.set(JSON.stringify(
|
||||||
|
this.configuration.uniqueAssign != null ? this.configuration.uniqueAssign(element) :
|
||||||
|
this.configuration.valueAssign != null ? this.configuration.valueAssign(element) : element
|
||||||
|
), element);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSelectedItem(value: any) {
|
||||||
|
return this._selectedItems.get(JSON.stringify(this.configuration.uniqueAssign != null ? this.configuration.uniqueAssign(value) : value));
|
||||||
|
}
|
||||||
|
|
||||||
filter(query: string): Observable<any[]> {
|
filter(query: string): Observable<any[]> {
|
||||||
// If loadDataOnStart is enabled and query is empty we return the initial items.
|
// If loadDataOnStart is enabled and query is empty we return the initial items.
|
||||||
if (this.isNullOrEmpty(query) && this.loadDataOnStart) {
|
if (this.isNullOrEmpty(query) && this.loadDataOnStart) {
|
||||||
return this.configuration.initialItems(this.configuration.extraData) || observableOf([]);
|
return this.configuration.initialItems(this.value || [], this.configuration.extraData) || observableOf([]);
|
||||||
} else if (query && query.length >= this.minFilteringChars) {
|
} else if (query && query.length >= this.minFilteringChars) {
|
||||||
if (this.configuration.filterFn) {
|
if (this.configuration.filterFn) {
|
||||||
return this.configuration.filterFn(query, this.configuration.extraData);
|
return this.configuration.filterFn(query, this.value || [], this.configuration.extraData);
|
||||||
} else {
|
} else {
|
||||||
return this.configuration.initialItems(this.configuration.extraData) || observableOf([]);
|
return this.configuration.initialItems(this.value || [], this.configuration.extraData) || observableOf([]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return observableOf([]);
|
return observableOf([]);
|
||||||
|
@ -258,7 +256,7 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp
|
||||||
}
|
}
|
||||||
|
|
||||||
stringify(value: any): string {
|
stringify(value: any): string {
|
||||||
return typeof (value) == 'string' ? value : JSON.stringify(value);
|
return JSON.stringify(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
isNullOrEmpty(query: string): boolean {
|
isNullOrEmpty(query: string): boolean {
|
||||||
|
@ -266,16 +264,16 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp
|
||||||
}
|
}
|
||||||
|
|
||||||
_optionSelected(event: MatAutocompleteSelectedEvent) {
|
_optionSelected(event: MatAutocompleteSelectedEvent) {
|
||||||
// this.optionSelectedInternal(event.option.value);
|
this.optionSelectedInternal(event.option.value);
|
||||||
this.onSelectAutoCompleteValue.next(event.option.value);
|
|
||||||
this.autocompleteInput.nativeElement.value = '';
|
this.autocompleteInput.nativeElement.value = '';
|
||||||
|
this._items = null; // refresh excluding previous item selected
|
||||||
}
|
}
|
||||||
|
|
||||||
private optionSelectedInternal(item: any) {
|
private optionSelectedInternal(item: any) {
|
||||||
const newValue = this._valueToAssign(item);
|
const newValue = this._valueToAssign(item);
|
||||||
|
|
||||||
//Update selected items
|
//Update selected items
|
||||||
this._selectedItems.set(this.stringify(newValue), item);
|
this._selectedItems.set(JSON.stringify(this.configuration.uniqueAssign != null ? this.configuration.uniqueAssign(newValue) : newValue), item);
|
||||||
|
|
||||||
const newValueArray = (Array.isArray(this.value) ? this.value : []);
|
const newValueArray = (Array.isArray(this.value) ? this.value : []);
|
||||||
newValueArray.push(newValue);
|
newValueArray.push(newValue);
|
||||||
|
@ -289,18 +287,14 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp
|
||||||
this.inputValue = event.target.value;
|
this.inputValue = event.target.value;
|
||||||
// prevent filtering results if arrow were pressed
|
// prevent filtering results if arrow were pressed
|
||||||
if (event.keyCode !== ENTER && (event.keyCode < 37 || event.keyCode > 40)) {
|
if (event.keyCode !== ENTER && (event.keyCode < 37 || event.keyCode > 40)) {
|
||||||
|
|
||||||
|
|
||||||
|
if (!this._items) { // possibly from an error request here
|
||||||
|
this._onInputFocus();
|
||||||
|
}
|
||||||
|
|
||||||
this._inputSubject.next(this.inputValue);
|
this._inputSubject.next(this.inputValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (event.keyCode !== ENTER && (event.keyCode < 37 || event.keyCode > 40) && event.keyCode !== COMMA) {
|
|
||||||
// this._inputSubject.next(this.inputValue);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
public onKeyDown(event) {
|
|
||||||
if (event.keyCode === BACKSPACE && event.target.value.length === 0 && this._selectedItems.size > 0) {
|
|
||||||
this._removeSelectedItem(Array.from(this._selectedItems.values()).pop(), event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setValue(value: any) {
|
private _setValue(value: any) {
|
||||||
|
@ -316,68 +310,45 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp
|
||||||
debounceTime(this.requestDelay),
|
debounceTime(this.requestDelay),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
tap(query => this.queryValue = query),
|
mergeMap(query => this.filter(query)),
|
||||||
switchMap(query => (!this.minLength || (query && query.length >= this.minLength)) ? this.filter(query) : observableOf([])));
|
catchError(error => {
|
||||||
|
this._items = null;
|
||||||
|
console.error(error);
|
||||||
|
return of([])
|
||||||
|
})
|
||||||
|
|
||||||
if (this.configuration.groupingFn) {
|
);
|
||||||
this._groupedItems = this._items.pipe(map(items => this.configuration.groupingFn(items)));
|
|
||||||
}
|
if (this.configuration.groupingFn) { this._groupedItems = this._items.pipe(map(items => this.configuration.groupingFn(items))); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onBlur($event: FocusEvent) {
|
public onBlur($event: MouseEvent) {
|
||||||
if (!this.selected) {
|
if (this.inputValue.length > 1 && this.autocomplete.options && this.autocomplete.options.length > 0 && this.autoSelectFirstOptionOnBlur) {
|
||||||
if (this.inputValue && this.inputValue.length > 1 && this.autocomplete.options && this.autocomplete.options.length > 0 && this.autoSelectFirstOptionOnBlur) {
|
this.optionSelectedInternal(this.autocomplete.options.first.value);
|
||||||
// this.optionSelectedInternal(this.autocomplete.options.first.value);
|
|
||||||
this.valueOnBlur.next(this.autocomplete.options.first.value);
|
|
||||||
} else if (this.inputValue && this.inputValue.length > 1 && this.autoSelectFirstOptionOnBlur) {
|
|
||||||
this.valueOnBlur.next(this.inputValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear text if not an option
|
|
||||||
if (this.inputValue && this.inputValue.length > 1) {
|
|
||||||
this.inputValue = '';
|
|
||||||
document.getElementById((<HTMLInputElement>$event.target).id).focus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onChange = (_: any) => {
|
onChange = (_: any) => { };
|
||||||
};
|
private _onTouched = () => { };
|
||||||
private _onTouched = () => {
|
|
||||||
};
|
|
||||||
|
|
||||||
writeValue(value: any): void {
|
writeValue(value: any): void {
|
||||||
this.value = Array.isArray(value) ? value : null;
|
this.value = Array.isArray(value) ? value : null;
|
||||||
// Update chips observable
|
// Update chips observable
|
||||||
this.getSelectedItems(value);
|
this._items = null;
|
||||||
}
|
this.getSelectedItems(this.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(' ');
|
|
||||||
}
|
}
|
||||||
|
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) {
|
onContainerClick(event: MouseEvent) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (this.disabled) {
|
if (this.disabled) { return; }
|
||||||
return;
|
if (this.autocompleteInput) {
|
||||||
|
this.fm.focusVia(this.autocompleteInput.nativeElement, 'program');
|
||||||
}
|
}
|
||||||
this._onInputFocus();
|
this._onInputFocus();
|
||||||
if (!this.autocomplete.isOpen) {
|
if (!this.autocomplete.isOpen) {
|
||||||
|
@ -388,25 +359,18 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.stateChanges.complete();
|
this.stateChanges.complete();
|
||||||
this.fm.stopMonitoring(this.elRef.nativeElement);
|
this.fm.stopMonitoring(this.elRef.nativeElement);
|
||||||
if (this.valueAssignSubscription) {
|
this.gettingSelectedItem$.complete();
|
||||||
this.valueAssignSubscription.unsubscribe();
|
|
||||||
this.valueAssignSubscription = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Configuration getters
|
//Configuration getters
|
||||||
_displayFn(item: any): string {
|
_displayFn(item: any): string {
|
||||||
// console.log('_displayFn' + item);
|
// console.log('_displayFn' + item);
|
||||||
if (this.configuration.displayFn && item) {
|
if (this.configuration.displayFn && item) { return this.configuration.displayFn(item); }
|
||||||
return this.configuration.displayFn(item);
|
|
||||||
}
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
_titleFn(item: any): string {
|
_titleFn(item: any): string {
|
||||||
if (this.configuration.titleFn && item) {
|
if (this.configuration.titleFn && item) { return this.configuration.titleFn(item); }
|
||||||
return this.configuration.titleFn(item);
|
|
||||||
}
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,28 +385,22 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectedValueTemplate(item: any): TemplateRef<any> {
|
_selectedValueTemplate(item: any): TemplateRef<any> {
|
||||||
if (this.configuration.selectedValueTemplate && item) {
|
if (this.configuration.selectedValueTemplate && item) { return this.configuration.selectedValueTemplate; }
|
||||||
return this.configuration.selectedValueTemplate;
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_subtitleFn(item: any): string {
|
_subtitleFn(item: any): string {
|
||||||
if (this.configuration.subtitleFn && item) {
|
if (this.configuration.subtitleFn && item) { return this.configuration.subtitleFn(item); }
|
||||||
return this.configuration.subtitleFn(item);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_valueToAssign(item: any): any {
|
_valueToAssign(item: any): any {
|
||||||
if (this.configuration.valueAssign && item) {
|
if (this.configuration.valueAssign && item) { return this.configuration.valueAssign(item); }
|
||||||
return this.configuration.valueAssign(item);
|
|
||||||
}
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
get requestDelay(): number {
|
get requestDelay(): number {
|
||||||
return this.configuration.requestDelay != null ? this.configuration.requestDelay : 600;
|
return this.configuration.requestDelay != null ? this.configuration.requestDelay : 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
get minFilteringChars(): number {
|
get minFilteringChars(): number {
|
||||||
|
@ -457,40 +415,35 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp
|
||||||
return this.configuration.autoSelectFirstOptionOnBlur != null ? this.configuration.autoSelectFirstOptionOnBlur : false;
|
return this.configuration.autoSelectFirstOptionOnBlur != null ? this.configuration.autoSelectFirstOptionOnBlur : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get popupItemActionIcon(): string {
|
|
||||||
return this.configuration.popupItemActionIcon != null ? this.configuration.popupItemActionIcon : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
get appendClassToItem(): { class: string, applyFunc: (item: any) => boolean }[] {
|
|
||||||
return this.configuration.appendClassToItem !== null ? this.configuration.appendClassToItem : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Chip Functions
|
//Chip Functions
|
||||||
_addItem(event: MatChipInputEvent): void {
|
_addItem(event: MatChipInputEvent): void {
|
||||||
const input = event.input;
|
const input = event.input;
|
||||||
const value = event.value;
|
const value = event.value;
|
||||||
// Add our fruit
|
// Add our fruit
|
||||||
if (value.trim()) {
|
// if ((value || '').trim()) {
|
||||||
// this.optionSelectedInternal(value);
|
// this.selectedItems.push(value.trim());
|
||||||
this.valueOnBlur.next(value);
|
// }
|
||||||
}
|
|
||||||
// Reset the input value
|
// Reset the input value
|
||||||
if (input) {
|
if (input) {
|
||||||
this.inputValue = '';
|
this.inputValue = '';
|
||||||
}
|
}
|
||||||
//this.inputFormControl.setValue(null);
|
//this.inputFormControl.setValue(null);
|
||||||
}
|
}
|
||||||
|
public reset(): void {
|
||||||
|
this._inputSubject.unsubscribe();
|
||||||
|
this._inputSubject = new Subject<string>();
|
||||||
|
this._items = null;
|
||||||
|
}
|
||||||
|
|
||||||
_removeSelectedItem(item: any, event: MouseEvent): void {
|
_removeSelectedItem(item: any, event: MouseEvent): void {
|
||||||
if (event != null) {
|
if (event != null) { event.stopPropagation(); }
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
const valueToDelete = this._valueToAssign(item);
|
const valueToDelete = this._valueToAssign(item);
|
||||||
this.value = this.value.filter(x => this.stringify(x) !== this.stringify(valueToDelete)); //TODO, maybe we need to implement equality here differently.
|
this.value = this.value.filter(x => JSON.stringify(x) !== JSON.stringify(valueToDelete)); //TODO, maybe we need to implement equality here differently.
|
||||||
this.optionRemoved.emit(item);
|
this.optionRemoved.emit(item);
|
||||||
|
this._items = null; // refresh query with excluded items
|
||||||
|
|
||||||
//Update chips
|
//Update chips
|
||||||
this._selectedItems.delete(this.stringify(valueToDelete));
|
this._selectedItems.delete(JSON.stringify(valueToDelete));
|
||||||
|
|
||||||
this.autocompleteInput.nativeElement.focus();
|
this.autocompleteInput.nativeElement.focus();
|
||||||
this.pushChanges(this.value);
|
this.pushChanges(this.value);
|
||||||
|
@ -503,9 +456,15 @@ export class MultipleAutoCompleteComponent extends _CustomComponentMixinBase imp
|
||||||
this.optionActionClicked.emit(item);
|
this.optionActionClicked.emit(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get popupItemActionIcon(): string {
|
||||||
|
return this.configuration.popupItemActionIcon != null ? this.configuration.popupItemActionIcon : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
get appendClassToItem(): { class: string, applyFunc: (item: any) => boolean }[] {
|
||||||
|
return this.configuration.appendClassToItem !== null ? this.configuration.appendClassToItem : null;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly empyObj = {};
|
private readonly empyObj = {};
|
||||||
|
|
||||||
computeClass(value: any) {
|
computeClass(value: any) {
|
||||||
if (!(this.appendClassToItem && this.appendClassToItem.length)) {
|
if (!(this.appendClassToItem && this.appendClassToItem.length)) {
|
||||||
return this.empyObj;
|
return this.empyObj;
|
||||||
|
|
Loading…
Reference in New Issue