projects/rebirth-ng/src/lib/auto-complete/auto-complete.directive.ts
OnInit
OnDestroy
ControlValueAccessor
providers |
{
: , : (() => ), : true
}
|
selector | [reAutoComplete] |
Properties |
|
Methods |
|
Inputs |
Outputs |
HostListeners |
constructor(elementRef: ElementRef, viewContainerRef: ViewContainerRef, componentFactoryResolver: ComponentFactoryResolver, renderer: Renderer2, injector: Injector, positionService: PositionService, rebirthNGConfig: RebirthNGConfig, changeDetectorRef: ChangeDetectorRef)
|
|||||||||||||||||||||||||||
Parameters :
|
appendBody
|
Default value: |
cssClass
|
Type: |
dataSource
|
Type: |
delay
|
Type: |
disabled
|
Type: |
formatter
|
Type: |
itemTemplate
|
Type: |
minLength
|
Type: |
noResultItemTemplate
|
Type: |
onSearch
|
Type: |
placementElement
|
Type: |
selectValueChange
|
$event type: EventEmitter
|
blur |
blur()
|
document:click |
Arguments : '$event'
|
document:click($event: Event)
|
keydown.ArrowDown |
Arguments : '$event'
|
keydown.ArrowDown($event: )
|
keydown.ArrowUp |
Arguments : '$event'
|
keydown.ArrowUp($event: )
|
keydown.Enter |
Arguments : '$event'
|
keydown.Enter($event: )
|
keydown.esc |
Arguments : '$event'
|
keydown.esc($event: )
|
Private fillPopup |
fillPopup(source?: any, term?: string)
|
Returns :
void
|
Private hidePopup |
hidePopup()
|
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Returns :
void
|
ngOnInit |
ngOnInit()
|
Returns :
void
|
onSourceChange | |||||||||
onSourceChange(source: , term?: string)
|
|||||||||
Parameters :
Returns :
void
|
Private onTermChange | ||||
onTermChange(term: )
|
||||
Parameters :
Returns :
void
|
positionPopup |
positionPopup()
|
Returns :
void
|
Private registerInputEvent | ||||||
registerInputEvent(elementRef: ElementRef)
|
||||||
Parameters :
Returns :
any
|
registerOnChange | ||||||
registerOnChange(fn: any)
|
||||||
Parameters :
Returns :
void
|
registerOnTouched | ||||||
registerOnTouched(fn: any)
|
||||||
Parameters :
Returns :
void
|
Private removePopView |
removePopView()
|
Returns :
void
|
setDisabledState | ||||||
setDisabledState(isDisabled: boolean)
|
||||||
Parameters :
Returns :
void
|
Private setupArraySource |
setupArraySource()
|
Returns :
void
|
toggle | ||||||
toggle($event?: Event)
|
||||||
Parameters :
Returns :
void
|
Private unSubscription |
unSubscription()
|
Returns :
void
|
Private writeInputValue | ||||||
writeInputValue(value: any)
|
||||||
Parameters :
Returns :
void
|
writeValue | ||||||
writeValue(obj: any)
|
||||||
Parameters :
Returns :
void
|
Private arraySource |
arraySource:
|
Type : any[]
|
Private onChange |
onChange:
|
Default value : (_: any) => null
|
Private onTouched |
onTouched:
|
Default value : () => null
|
Private placement |
placement:
|
Type : string
|
Default value : 'bottom-left'
|
Private popupRef |
popupRef:
|
Type : ComponentRef<AutoCompletePopupComponent>
|
Private subscription |
subscription:
|
Type : Subscription
|
term |
term:
|
Type : string
|
Private value |
value:
|
Type : any
|
Private valueChanges |
valueChanges:
|
Type : Observable<any[]>
|
dataSource | ||||||
setdataSource(dataSource: [])
|
||||||
Parameters :
Returns :
void
|
import {
ChangeDetectorRef,
ComponentFactoryResolver,
ComponentRef,
Directive,
ElementRef,
EventEmitter,
forwardRef,
HostListener,
Injector,
Input,
OnDestroy,
OnInit,
Output,
Renderer2,
TemplateRef,
ViewContainerRef
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { fromEvent } from 'rxjs';
import { of } from 'rxjs';
import { Observable } from 'rxjs';
import { Subscription } from 'rxjs';
import { map, filter, debounceTime, switchMap, tap } from 'rxjs/operators';
import { RebirthNGConfig } from '../rebirth-ng.config';
import { PositionService } from '../position/positioning.service';
import { AutoCompletePopupComponent } from './auto-complete-popup.component';
import { stopPropagationIfExist } from '../utils/dom-utils';
@Directive({
selector: '[reAutoComplete]',
exportAs: 'autoComplete',
host: {
'autocomplete': 'off',
'autocapitalize': 'off',
'autocorrect': 'off'
},
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AutoCompleteDirective),
multi: true
}]
})
export class AutoCompleteDirective implements OnInit, OnDestroy, ControlValueAccessor {
@Input() disabled: boolean;
@Input() cssClass: string;
@Input() delay: number;
@Input() minLength: number;
@Input() appendBody = false;
@Input() itemTemplate: TemplateRef<any>;
@Input() noResultItemTemplate: TemplateRef<any>;
@Input() formatter: (item: any) => string;
@Input() onSearch: (term: string, target?: AutoCompleteDirective) => Observable<any[]>;
@Output() selectValueChange = new EventEmitter<any>();
@Input() placementElement: any;
term: string;
private valueChanges: Observable<any[]>;
private value: any;
private placement = 'bottom-left';
private subscription: Subscription;
private arraySource: any[];
private popupRef: ComponentRef<AutoCompletePopupComponent>;
private onChange = (_: any) => null;
private onTouched = () => null;
constructor(private elementRef: ElementRef, private viewContainerRef: ViewContainerRef,
private componentFactoryResolver: ComponentFactoryResolver, private renderer: Renderer2,
private injector: Injector, private positionService: PositionService,
private rebirthNGConfig: RebirthNGConfig, private changeDetectorRef: ChangeDetectorRef) {
this.delay = rebirthNGConfig.autoComplete.delay;
this.minLength = rebirthNGConfig.autoComplete.minLength;
this.itemTemplate = rebirthNGConfig.autoComplete.itemTemplate;
this.noResultItemTemplate = rebirthNGConfig.autoComplete.noResultItemTemplate;
this.formatter = rebirthNGConfig.autoComplete.formatter;
}
ngOnInit() {
this.valueChanges = this.registerInputEvent(this.elementRef);
this.subscription = this.valueChanges
.subscribe(source => this.onSourceChange(source));
const factory = this.componentFactoryResolver.resolveComponentFactory(AutoCompletePopupComponent);
const viewContainerRef = this.appendBody ? this.rebirthNGConfig.rootContainer : this.viewContainerRef;
// EXCEPTION: Expression has changed after it was checked when append to body;
setTimeout(() => {
this.popupRef = viewContainerRef.createComponent(factory, this.viewContainerRef.length, this.injector);
this.fillPopup();
this.positionPopup();
this.popupRef.instance.registerOnChange(item => {
this.value = item;
this.writeInputValue(item);
this.onChange(item);
this.hidePopup();
this.selectValueChange.emit(item);
});
}, 0);
}
@Input()
set dataSource(dataSource: any[]) {
if (dataSource) {
this.arraySource = dataSource;
this.setupArraySource();
}
}
private setupArraySource() {
if (this.popupRef) {
this.popupRef.instance.source = this.arraySource;
}
this.onSearch = (term) => {
return of(
this.arraySource
.filter(item => this.formatter(item).toLowerCase().indexOf((term || '').toLowerCase()) !== -1)
);
};
}
writeValue(obj: any): void {
this.value = obj || '';
this.writeInputValue(this.value);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', isDisabled);
if (this.popupRef) {
this.popupRef.instance.setDisabledState(isDisabled);
}
}
ngOnDestroy() {
this.unSubscription();
this.removePopView();
}
private removePopView() {
if (this.popupRef) {
this.popupRef.destroy();
}
}
@HostListener('blur', [])
onBlur() {
this.onTouched();
}
@HostListener('keydown.esc', ['$event'])
onEscKeyup($event) {
this.hidePopup();
}
@HostListener('keydown.Enter', ['$event'])
onEnterKeyDown($event) {
if (this.popupRef && this.popupRef.instance.isOpen) {
$event.preventDefault();
$event.stopPropagation();
this.popupRef.instance.selectCurrentItem();
this.hidePopup();
}
}
@HostListener('keydown.ArrowUp', ['$event'])
onArrowUpKeyDown($event) {
if (this.popupRef) {
$event.preventDefault();
$event.stopPropagation();
this.popupRef.instance.prev();
}
}
@HostListener('keydown.ArrowDown', ['$event'])
onArrowDownKeyDown($event) {
if (this.popupRef) {
$event.preventDefault();
$event.stopPropagation();
this.popupRef.instance.next();
}
}
@HostListener('document:click', ['$event'])
onDocumentClick($event: Event) {
const hostElement = this.elementRef.nativeElement;
if ($event.target !== hostElement) {
this.hidePopup();
}
}
onSourceChange(source, term?: string) {
if (term !== undefined) {
this.value = term;
}
const pop = this.popupRef.instance;
pop.reset();
this.fillPopup(source, this.value);
if ((source && source.length) || this.noResultItemTemplate) {
pop.show();
this.positionPopup();
this.changeDetectorRef.markForCheck();
}
}
private hidePopup() {
if (this.popupRef) {
this.popupRef.instance.activeIndex = 0;
this.popupRef.instance.hide();
}
}
toggle($event?: Event) {
stopPropagationIfExist($event);
if (this.popupRef) {
const pop = this.popupRef.instance;
if (pop.isOpen) {
this.hidePopup();
return;
}
this.onSourceChange(this.arraySource);
}
}
positionPopup() {
const targetElement = this.popupRef.location.nativeElement;
const hostElement = this.placementElement || this.elementRef.nativeElement;
const clientRect = this.positionService.positionElements(hostElement, targetElement, this.placement, this.appendBody);
this.renderer.setStyle(targetElement, 'left', `${clientRect.left}px`);
this.renderer.setStyle(targetElement, 'top', `${clientRect.top}px`);
}
private fillPopup(source?: any, term?: string) {
const pop = this.popupRef.instance;
pop.source = source;
pop.term = term;
['formatter', 'itemTemplate', 'noResultItemTemplate', 'cssClass']
.forEach(key => {
if (this[key] !== undefined) {
pop[key] = this[key];
}
});
}
private writeInputValue(value: any) {
const formatValue = value || '';
this.renderer.setProperty(this.elementRef.nativeElement, 'value', this.formatter(formatValue) || '');
}
private unSubscription() {
if (this.subscription) {
this.subscription.unsubscribe();
this.subscription = null;
}
}
private onTermChange(term) {
this.value = term;
if (this.popupRef) {
this.popupRef.instance.term = term;
}
this.onChange(term);
}
private registerInputEvent(elementRef: ElementRef) {
return fromEvent(elementRef.nativeElement, 'input')
.pipe(
map((e: any) => e.target.value),
tap(term => this.onTouched()),
tap(term => this.term = term),
filter(term => !this.disabled && this.onSearch && term.length >= this.minLength),
debounceTime(this.delay),
// distinctUntilChanged(),
tap(term => this.onTermChange(term)),
switchMap(term => this.onSearch(term, this))
);
}
}