angular - 角垫表和移位点击选择
问题描述
我正在尝试在MatDataTable
使用角度和打字稿的排序上实现移位单击功能。
基本的失败是,每当在表上注册点击事件时,都会存储选择的行。
如果检测到移位单击,组件将尝试在最后选定的行和当前选定的行之间进行选择(就像 Windows 中的移位单击选择一样)。
我拥有的事件处理代码如下:
clickHandler(event, row, index) {
console.log('index clicked: ' + index);
if (event.ctrlKey) {
this.selectRows(row, index); // This records this.lastSelected
} else if (event.shiftKey) {
this.selectRowsFill(row, index);
} else {
this.selectElement(row, index); // As does this call.
}
}
// This function computes a start and end point for autoselection of
// rows between this.lastSelected, and the currently clicked row
selectRowsFill(row, index) {
const indexA = this.lastSelected;
const indexB = index;
if (indexA > indexB) {
// Descending order
this.selectRowsBetween(indexB, indexA);
} else {
// Ascending order
this.selectRowsBetween(indexA, indexB);
}
}
// And this performs the actual selection.
private selectRowsBetween(start, end) {
let currentIndex = 0;
this.dataSource.data.forEach(row => {
if (currentIndex >= start && currentIndex <= end) {
this.selection.select(row);
}
currentIndex++;
});
}
和 HTML:
<mat-row *matRowDef="let row; let i = index; columns: cols;" (click)="clickHandler($event, row, i)" [ngClass]="{'inDatabase' : isAdded(row), 'highlight': isSelectedAndAdded(row) || isSelected(row) }">
只要表格未排序,此代码就可以正常工作。一旦我对 应用排序算法MatTableDataSource
,它就会改变数据的顺序,导致选择出现故障。看起来选择是基于 中数据的原始(未排序)顺序MatTableDataSource
,这是有道理的。
那么如何让 shift click selection 处理已排序的数据,而不是未排序的数据?
解决方案
这是我的解决方案:>STACKBLITZ<
代码保存以防 STACKBLITZ 离线
- html
<table [shiftClickSource]="dataSource"
[shiftClickSelectModel]="selection"
[shiftClickSourceId]="['position']"
mat-table [dataSource]="dataSource" class="mat-elevation-z8" matSort>
....
- ts(指令)
import { Directive, Input, HostListener, OnInit, OnDestroy, ElementRef } from '@angular/core';
import {SelectionModel, SelectionChange} from '@angular/cdk/collections';
import { Subject, BehaviorSubject, Observable, merge, pipe, } from 'rxjs';
import { shareReplay, takeUntil, withLatestFrom, tap } from 'rxjs/operators';
import {MatTable, MatTableDataSource} from '@angular/material/table';
/**
* Directive that adds shift-click selection to your mat-table.
* It needs the datasource from the host, the selectionModel of the checkboxes
* and optionally an array of [ids] to find the correct rows in the
* (possibly sorted/ filtered) source array.
*/
@Directive({
selector: '[shiftClickSource]'
})
export class ShiftClickDirective implements OnInit, OnDestroy {
finishHim = new Subject<void>();
lastItem: any;
lastShiftSelection: any[];
alwaysTheRightSourceArray$: Observable<any>;
// the always right source array :-)
source: any[];
shiftHolding$ = new BehaviorSubject<boolean>(false);
//click that happens on tbody of mat-table
click$ = new Subject<void>();
// observable to record the last change of the checkbox selectionModel
recordSelectionChange$: Observable<SelectionChange<any>>;
@HostListener('document:keydown.shift', ['$event'])
shiftDown(_) {
this.shiftHolding$.next(true);
}
@HostListener('document:keyup.shift', ['$event'])
shiftUp(event: KeyboardEvent) {
this.shiftHolding$.next(false);
}
// datasource that is used on the Checkbox-Table
@Input('shiftClickSource') dataSource: MatTableDataSource<any>;
// Is optional, if id col is known this will improve performance.
@Input('shiftClickSourceId') idArr: string[];
@Input('shiftClickSelectModel') selection: SelectionModel<any>;
constructor(private host: ElementRef) {}
ngOnInit() {
// data can change order (sorting) and can be reduced (filtering)
this.alwaysTheRightSourceArray$ = this.dataSource.connect()
.pipe(
tap(_ => {
this.source = this.dataSource
.sortData(
this.dataSource.filteredData,
this.dataSource.sort
);
})
);
// lets record the last change of
this.recordSelectionChange$ = this.selection.changed.pipe(
shareReplay(1)
)
// clicks on tbody mean that we need to do something
this.host.nativeElement.childNodes[1].addEventListener("click", function() {
this.click$.next();
}.bind(this));
const reactOnClickOnTbody$ =
this.click$.pipe(
withLatestFrom(this.shiftHolding$.asObservable()),
withLatestFrom(this.recordSelectionChange$, (arr, c): [SelectionChange<any>, boolean ] => [c, arr[1]] ),
tap( arr => {
const v = arr[0];
const sh = arr[1];
const ans = [...v.added, ...v.removed][0];
if ( sh && this.lastItem ) {
this.onTbodyClicked(this.lastItem, ans);
} else {
this.lastItem = ans;
this.lastShiftSelection = undefined;
// console.log('clear');
}
}),
);
merge(
reactOnClickOnTbody$,
this.alwaysTheRightSourceArray$
).pipe(takeUntil(this.finishHim.asObservable()))
.subscribe();
}
// This function does all the real work.
onTbodyClicked(from: object, to: object) {
// console.log('onTbodyClickedRuns');
const fIdx = this.getIndexBasedOnData(from);
const tIdx = this.getIndexBasedOnData(to);
let ans;
let ans_last;
if( fIdx > tIdx ) {
ans = this.source.slice(tIdx, fIdx);
} else {
// console.log('seconds index case');
ans = this.source.slice(fIdx +1 , tIdx + 1);
}
if (this.lastShiftSelection) {
this.selection['_emitChanges']=false;
this.selection.deselect(...this.lastShiftSelection);
this.selection['_emitChanges']=true;
}
this.lastShiftSelection = [...ans];
if( fIdx > tIdx ) {
ans_last = ans.shift();
} else {
ans_last = ans.pop();
}
// console.log(this.lastShiftSelection);
const cond = ans.every(el => this.selection.isSelected(el)) && !this.selection.isSelected(ans_last)
if ( cond ) {
// console.log('deselect')
this.selection['_emitChanges']=false;
this.selection.deselect(...ans);
this.selection['_emitChanges']=true;
} else {
// console.log('select')
this.selection['_emitChanges']=false;
this.selection.select(...ans, ans_last);
this.selection['_emitChanges']=true;
}
}
// helper function
private getIndexBasedOnData(row, source = this.source): number {
let ind: number;
if (this.idArr) {
ind = source.findIndex( _d => this.idArr.every(id => _d[id] === row[id] ));
} else {
ind = source.findIndex( _d => Object.keys(row).every(k => _d[k] === row[k] ));
}
return ind;
}
ngOnDestroy() {
this.finishHim.next();
this.finishHim.complete();
}
}
推荐阅读
- html - 如何将背景颜色覆盖添加到 div?
- python - python中的整数应该是无限的,但我得到了OverflowError:(34,'结果太大')
- flutter - 如何使用gridview在checkboxlisttile右侧底部编码int文本
- java - java swing组件没有显示while循环
- apache-spark - pyspark 将字符串拆分为键值对并提取某些值
- django - GitLab CI 为 Postgres 生成超过 63 个字符限制的数据库名称(django.core.exceptions.ImproperlyConfigured)
- javascript - useReducer 使用异步操作来发出 API 请求
- java - 使用凭据改造授权标头
- python - 从 FastAPI 发送时流体已满,请求接收时为空
- elasticsearch - Elasticsearch 7.10 如何赋予文档中较早出现的术语更多的权重