import { Component, ElementRef, ChangeDetectorRef, Inject, Renderer2, forwardRef, ChangeDetectionStrategy, ViewEncapsulation, Input, Injector, OnInit, OnChanges, SimpleChanges, ViewChild, HostBinding, HostListener } from '@angular/core'; import { DOCUMENT } from '@angular/common'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { DatalistComponent } from '@farris/ui-datalist'; import { BaseComboComponent } from './base-combo.component'; import { SelectItem } from './combo.interface'; import { ComboService } from './combo.service'; import { filter, take, takeUntil } from 'rxjs/operators'; import { Key } from 'protractor'; export const INPUT_COMBO_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ComboListComponent), multi: true }; @Component({ selector: 'farris-combo-list', templateUrl: './combo-list.component.html', // styleUrls: ['./combo-list.component.scss'], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [INPUT_COMBO_VALUE_ACCESSOR, ComboService] }) export class ComboListComponent extends BaseComboComponent implements OnInit, OnChanges { @Input() itemTemplate: any; /** * 显示面板方式: click | focus, 默认: click */ @Input() showPanelType: 'click'|'focus' = 'click'; @ViewChild('dl') dataListRef: DatalistComponent; @Input() itemHeight: number; @HostBinding('class.f-combo-list') cls = true; @HostBinding('class.f-combo-list--tag') get clsTag() { return this.viewType === 'tag'; } private filterData: any[] = []; constructor( public el: ElementRef, public cdr: ChangeDetectorRef, @Inject(DOCUMENT) public document: any, public render: Renderer2, public comboService: ComboService, public injector: Injector ) { super(el, cdr, document, render, comboService, injector); } ngOnInit() { super.ngOnInit(); if (this.multiSelect) { this.enableCancelSelected = this.multiSelect; } this.remoteSearch = false; } ngOnChanges(changes: SimpleChanges): void { /** * 如果直接绑定一个数组作为列表数据并且 data 是异步数据,需要此方法,同步displayText与selectedValues */ if (changes.data && !changes.data.firstChange && !this.uri && !this.nosearch) { const datas = changes.data.currentValue; const val = this.selectedValues || this.displayText; if (val !== undefined && val !== null && datas && datas.length > 0) { const textArr = this.filterSelections(val, this.data); this.displayText = textArr.filter(v => !!v).join(this.separator); this.updateSelections(val, this.data); this.comboService.selections$.next({action: 'initData'}); } else { this.selections = []; this.displayText = ''; } } // if (changes.multiSelect && !changes.multiSelect.isFirstChange()) { // this.enableCancelSelected = this.multiSelect; // } } @HostListener('keydown', ['$event']) registerKeyboardEvent($event: KeyboardEvent) { if (this.multiSelect || (this.readonly || this.disabled)) { return; } if ($event.code === 'ArrowUp' || $event.code === 'ArrowDown' || (!this.editable && !this.readonly && !this.disabled && $event.code === 'Space')) { $event.preventDefault(); $event.stopPropagation(); } const selectItem = (index) => { this.comboService.selectItem(data[index], index, false); const params = {data: data[index], index}; if (this.isOpen) { this.scrollToCurrentItem(index); } this.updateSelectValues(params); }; let data = this.filterData && this.filterData.length ? this.filterData : this.data; if (!this['_cmbPanel']) { data = this.data; } if ($event.code === 'ArrowUp') { // up if (this.selectedValues) { const idx = data.findIndex(n => this.commonUtils.getValue(this.idField, n) === this.selectedValues); let prevIdx = idx - 1; if (prevIdx < 0) { prevIdx = data.length - 1; } selectItem(prevIdx); } } if ($event.code === 'ArrowDown' || ($event.code === 'Space' && !this.editable)) { // down const idx = data.findIndex(n => this.commonUtils.getValue(this.idField, n) === this.selectedValues); let nextIdx = idx + 1; if (nextIdx >= data.length) { nextIdx = 0; } selectItem(nextIdx); } } @HostListener('keydown.esc', ['$event']) escHandlerEvent($event: KeyboardEvent) { if (this.isOpen) { this.hide(false); } } protected onSelectionsChange(v: any[]) { if (this.uri) { if (this.selections && this.selections.length) { this.selectedValues = this.selections.map(n => n[this.idField]).join(this.separator); } } } private updateSelectValues(item: SelectItem, emitEvent = true) { const { data, index } = { ...item }; this.comboService.selectItem(data, index); if (this.dataListRef) { this.selectedValues = this.dataListRef.selectedValues; } if (!this.multiSelect) { this.selectedValues = data[this.idField]; } const _displayText = this.comboService.getValue(this.textField); if (emitEvent) { this.selectChange.emit({ data, index, instance: this, selections: this.selections }); this.cdr.markForCheck(); this.onValueChange({ text: _displayText, value: this.selectedValues, selections: this.selections }); } } private scrollToCurrentItem(index: number) { const ulEl = this.dataListRef.el.nativeElement.querySelector('ul.list-group'); if (ulEl && ulEl.children && ulEl.children[index]) { ulEl.children[index].scrollIntoView({block: 'nearest'}); } } onSelect(item: SelectItem) { this.updateSelectValues(item, true); if (!this.multiSelect) { this.comboService.isOpen$.next(false); } } onUnSelected(value: SelectItem) { const { data } = { ...value }; this.comboService.unSelectItem(data); if (this.dataListRef) { this.selectedValues = this.dataListRef.selectedValues; } // if (!this.multiSelect) { // this.selectedValues = data[this.idField]; // } const _displayText = this.comboService.getValue(this.textField); this.cdr.markForCheck(); this.onValueChange({ text: _displayText, value: this.selectedValues, selections: this.selections }); } onEnter(event: Event) { if (this.isOpen) { this.hide(); } } onKeyup($event) { // console.log('key up', $event); } onKeydown($event: KeyboardEvent) { // console.log('key down', $event); if ($event.code === 'Tab') { this.hide(false); } } onFocus($event) { if (this.showPanelType === 'focus') { this.show(); } } removeSelectItem($event: MouseEvent, item) { if ($event) { $event.stopPropagation(); } this.comboService.unSelectItem(item); this.selectedValues = this.comboService.getValue(this.idField); const _displayText = this.comboService.getValue(this.textField); this.cdr.detectChanges(); this.onValueChange({ text: _displayText, value: this.selectedValues, selections: this.selections }); } protected initDatasChangeAction() { this.comboService.data$.subscribe(data => { this.filterData = data; }); } }