如何在角材料表中获得选定区域?
Posted
技术标签:
【中文标题】如何在角材料表中获得选定区域?【英文标题】:How can I get selected area in angular material table? 【发布时间】:2020-07-31 04:01:38 【问题描述】:我想让用户像 Excel 表一样选择角表中的区域。如下图所示
我已经实现了点击多选行。
Stackblitz | multi selection rows
我不知道如何继续。
我发现了一些类似的插件,但不知道如何在 Angular 中实现这一点
-
Excel-style Table Cell Selector Plugin With jQuery - TableCellsSelection
Table Selection | CKEditor.com
任何帮助,建议表示赞赏
【问题讨论】:
【参考方案1】:但它并没有那么复杂stackblitz
如果我们的 tds 是这样的
<ng-container matColumnDef="position">
<th mat-header-cell *matHeaderCellDef> No. </th>
<td mat-cell #cell *matCellDef="let element;let i=index"
(click)="select($event,cell)"
<!--use isSelected(i,0) for the first column
isSelected(i,1) for the second column
...
-->
[ngClass]="'selected':isSelected(i,0)"
> element.position </td>
</ng-container>
我们使用 viewChildren 获取单元格,并使用选择声明一个数组
selection: number[]=[];
@ViewChildren("cell", read: ElementRef ) cells: QueryList<ElementRef>;
在单元格中,我们有从上到下和从左到右的所有“td”顺序
如果你按下了 shifh 键或 control 键,功能选择会考虑
select(event: MouseEvent, cell: any)
//search the cell "clicked"
const cellClick = this.cells.find(x => x.nativeElement == event.target);
//get the index of this cells
let indexSelected = -1;
this.cells.forEach((x, i) =>
if (x == cellClick) indexSelected = i;
);
if (event.ctrlKey) //if ctrl pressed
if (this.selection.indexOf(indexSelected)>=0) //if its yet selected
this.selection=this.selection.filter(x=>x!=indexSelected);
else //if it's not selected
this.selection.push(indexSelected)
else
if (event.shiftKey) //if the key shift is pressed
if (this.selection.length) //if there any more selected
//calculate the row and col of fisrt element we selected
let rowFrom=this.selection[0]%this.dataSource.data.length;
let colFrom=Math.floor(this.selection[0]/this.dataSource.data.length)
//idem from the index selected
let rowTo=indexSelected%this.dataSource.data.length;
let colTo=Math.floor(indexSelected/this.dataSource.data.length)
//interchange if from is greater than to
if (rowFrom>rowTo)
[rowFrom, rowTo] = [rowTo, rowFrom]
if (colFrom>colTo)
[colFrom, colTo] = [colTo, colFrom]
//clean the array
this.selection=[]
//we run througth all the td to check if we need push or not
this.cells.forEach((x,index)=>
const row=index%this.dataSource.data.length;
const col=Math.floor(index/this.dataSource.data.length)
if (row>=rowFrom && row<=rowTo && col>=colFrom && col<=colTo)
this.selection.push(index)
)
else //if there're anything selected and the shit is pressed
this.selection = [indexSelected]
else //if no key shit nor key ctrl
this.selection = [indexSelected]
一个函数 isSelected 提供了改变类的可能性
isSelected(row,column)
const index=column*this.dataSource.data.length+row
return this.selection.indexOf(index)>=0
带有 .css
table
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
td
outline: none!important
.selected
border:1px solid red;
更新
在示例中,您需要shift & ctrl
。但是我们可以有一个复选框或一个选择来“改变选择方式” - 使用 [(ngModel)] - 并替换条件 event.shiftKey 和 event.ctrlKey。
如果你总是选择一个范围,你可以使用两个变量“fromIndex”和“toIndex”,
fromIndex:number=-1
toIndex:number=-1
select(event: MouseEvent, cell: any)
....
..get the indexSelected ...
if (this.fromIndex==-1) //If nothing select
this.fromIndex=indexSelected
this.toIndex=-1
else
this.toIndex=indexSelected;
if (this.toIndex>=0 && this.fromIndex>=0)
...the code to select range
【讨论】:
完美。有什么方法可以在没有shift
&ctrl
按键的情况下选择单元格?像这个例子plnkr.co/edit/8wy5oMGEkLi1e9mg?preview
在示例中您需要shift & ctrl
。您可以使用复选框或选择来“更改选择方式”-使用 [(ngModel)]- 并替换条件 event.shiftKey 和 event.ctrlKey。如果您总是选择一个范围,则可以使用两个变量“fromIndex”和“toIndex”,请参阅更新后的答案【参考方案2】:
您可以借助自定义指令为 Angular 创建类似的插件。
Ng-run Example
它支持:
单击 - 选择单元格
Ctrl + 单击 - 切换单元格
Shift + 单击选择范围
单击并拖动以选择范围
rowSpan 和 colSpan 行为
range-selection.directive.ts
import Directive, ElementRef, Input, NgZone, OnDestroy, OnInit from '@angular/core';
import fromEvent, pipe, Subject from 'rxjs';
import filter, map, switchMap, takeUntil, tap from 'rxjs/operators';
@Directive(
selector: 'table[range-selection]',
)
export class RangeSelectionDirective implements OnDestroy, OnInit
@Input() selectionClass = 'state--selected';
selectedRange = new Set<htmlTableCellElement>();
private readonly table: HTMLTableElement;
private startCell: HTMLTableCellElement = null;
private cellIndices = new Map<HTMLTableCellElement, row: number; column: number >();
private selecting: boolean;
private destroy$ = new Subject<void>();
constructor(private zone: NgZone, nativeElement: ElementRef<HTMLTableElement>)
this.table = nativeElement;
ngOnInit()
this.zone.runOutsideAngular(() => this.initListeners());
private initListeners()
const withCell = pipe(
map((event: MouseEvent) => (event, cell: (event.target as HTMLElement).closest<HTMLTableCellElement>('th,td'))),
filter((cell) => !!cell),
);
const mouseDown$ = fromEvent<MouseEvent>(this.table, 'mousedown')
.pipe(
filter(event => event.button === 0),
withCell,
tap(this.startSelection)
);
const mouseOver$ = fromEvent<MouseEvent>(this.table, 'mouseover');
const mouseUp$ = fromEvent(document, 'mouseup').pipe(
tap(() => this.selecting = false)
);
this.handleOutsideClick();
mouseDown$.pipe(
switchMap(() => mouseOver$.pipe(takeUntil(mouseUp$))),
takeUntil(this.destroy$),
withCell
).subscribe(this.select);
private handleOutsideClick()
fromEvent(document, 'mouseup').pipe(
takeUntil(this.destroy$)
).subscribe((event: any) =>
if (!this.selecting && !this.table.contains(event.target as HTMLElement))
this.clearCells();
);
private startSelection = (cell, event: event: MouseEvent, cell: HTMLTableCellElement ) =>
this.updateCellIndices();
if (!event.ctrlKey && !event.shiftKey)
this.clearCells();
if (event.shiftKey)
this.select(cell);
this.selecting = true;
this.startCell = cell;
if (!event.shiftKey)
if (this.selectedRange.has(cell))
this.selectedRange.delete(cell);
else
this.selectedRange.add(cell);
cell.classList.toggle(this.selectionClass);
;
private select = (cell: cell: HTMLTableCellElement ) =>
this.clearCells();
this.getCellsBetween(this.startCell, cell).forEach(item =>
this.selectedRange.add(item);
item.classList.add(this.selectionClass);
);
;
private clearCells()
Array.from(this.selectedRange).forEach(cell =>
cell.classList.remove(this.selectionClass);
);
this.selectedRange.clear();
private getCellsBetween(start: HTMLTableCellElement, end: HTMLTableCellElement)
const startCoords = this.cellIndices.get(start);
const endCoords = this.cellIndices.get(end);
const boundaries =
top: Math.min(startCoords.row, endCoords.row),
right: Math.max(startCoords.column + start.colSpan - 1, endCoords.column + end.colSpan - 1),
bottom: Math.max(startCoords.row + start.rowSpan - 1, endCoords.row + end.rowSpan - 1),
left: Math.min(startCoords.column, endCoords.column),
;
const cells = [];
iterateCells(this.table, (cell) =>
const column, row = this.cellIndices.get(cell);
if (column >= boundaries.left && column <= boundaries.right &&
row >= boundaries.top && row <= boundaries.bottom)
cells.push(cell);
);
return cells;
private updateCellIndices()
this.cellIndices.clear();
const matrix = [];
iterateCells(this.table, (cell, y, x) =>
for (; matrix[y] && matrix[y][x]; x++)
for (let spanX = x; spanX < x + cell.colSpan; spanX++)
for (let spanY = y; spanY < y + cell.rowSpan; spanY++)
(matrix[spanY] = matrix[spanY] || [])[spanX] = 1;
this.cellIndices.set(cell, row: y, column: x);
);
ngOnDestroy()
this.destroy$.next();
function iterateCells(table: HTMLTableElement, callback: (cell: HTMLTableCellElement, y: number, x: number) => void): void
for (let y = 0; y < table.rows.length; y++)
for (let x = 0; x < table.rows[y].cells.length; x++)
callback(table.rows[y].cells[x], y, x);
Your Forked Stackblitz
【讨论】:
完美。唯一的困惑是我如何区分行?我检查了所选项目,但无法获取行详细信息 你有selectedRange
,这是一组单元格,每个单元格都有一个父级,即一行
我在 ParentElement 中找不到任何可以用来区分行的属性。我尝试将唯一 ID 添加到父元素以区分行。 stackblitz.com/edit/…
是否要获取所有选定的行?
尝试选择一些东西并查看控制台日志stackblitz.com/edit/…。它应该向您显示带有 rowId 的单元格列表【参考方案3】:
以下库提供类似的功能,但不是免费的。我不确定其他库
SyncFusion :
https://www.syncfusion.com/angular-ui-components/angular-spreadsheet#:~:text=The%20Angular%20Spreadsheet%20component%20can,Microsoft%20Excel%2097%2D2003%2
SyncFusion | Excel
https://ej2.syncfusion.com/angular/demos/?&_ga=2.263488904.50430700.1596175160-2025228585.1595393533#/material/spreadsheet/default
StackBlitz | Demo :
https://stackblitz.com/edit/angular-swdvq9?file=app.component.html
【讨论】:
【参考方案4】:我建议尝试一些似乎被 angular 社区使用的第三方库:
ag 网格:
https://www.ag-grid.com/javascript-grid-range-selection/
Plunker example 用于农业网格范围选择
[enableRangeSelection]="true"
素面
https://www.primefaces.org/primeng/showcase/#/table/sections
Stackblitz example 用于primefaces
【讨论】:
以上是关于如何在角材料表中获得选定区域?的主要内容,如果未能解决你的问题,请参考以下文章
如何避免使用角度材料从角度 2 中的 mat-table 传输相同的行