如何使 mat-table 行拖放与 cdkDragHandle 一起工作,以便只能使用句柄拖动该行?

How to make a mat-table row drag-drop work with cdkDragHandle so that the row is only draggable using the handle?

我找到了 this stackblitz 使用 angular cdk 添加拖放到 mat-table 的示例。但是,所需的行为是该行只能使用带有 cdkDragHandle 指令的元素进行拖动。在此示例中,您可以通过单击行上的任意位置来拖动元素。如何对此进行修改,以便只能使用拖动手柄拖动该行?

https://stackblitz.com/edit/angular-igmugp

恕我直言,除了 hacking/overriding Angular Material / CDK 的源代码外,没有对此的快速修复。 github: https://github.com/angular/material2/issues/13770 的开放功能请求证明了这一点。

问题是数据源/MatTable 上的 cdkDrag 会自动在所有子元素(生成行为)上创建拖动注释并且不能(轻易)覆盖。

根据文档 cdkDrag/cdkDragDisabled - cdkDragHandle/cdkDragHandleDisabled 应该有所帮助(它在没有 table 的情况下也能正常工作)。我已经升级了示例中的所有库以支持它们,但没有效果。

我通过将 cdkDrag 应用于 dragHandle 本身而不是行,并使用 cdkDragRootElement 来标识行来实现该 UX。它实现了通过句柄拖动的用户体验,但仍然存在一个错误,阻止了拖放后的实际重新排序。

See Stackblitz here.

Documentation of cdkDragRootElement is here.

Here is link to Github issue about it.

对于这个复杂的问题,我发现了一个稍微简单的问题。对于可拖动 tr 中的任何简单文本 td,我们可以使用 pointer-events:none 它将禁用所有文本元素。

在手柄图标上,使用 pointer-events:all 它将启用仅从图标拖动。

这也有禁用所有锚点和按钮的问题。因此,对于图标和按钮,请执行以下操作

  1. 使用 mouseDown 设置标志
  2. 开始拖动时,检查拖放鼠标弹起事件
  3. 拖动停止时,检查标志是否已设置并重置标志 return

检查这个 stackblits 的工作答案 https://stackblitz.com/edit/angular-rwzc76

对于此处发布的解决方案,我有一个替代解决方案。我的要求不仅仅是链接和需要更多交互性的纯文本单元格(包括文本选择、输入字段等)。

对将在 cdkDrag table 行 (tr) 中呈现的任何不可拖动的单元格使用指令,我能够停止 mousedown事件从向下冒泡到 cdkDropList 实例的活动 cdkDrag 行。

这是我的指令最终的样子。

import {Directive, ElementRef, OnDestroy, OnInit} from '@angular/core';

@Directive({
  selector: '[appCancelCdkDrag]'
})
export class CancelCdkDrag implements OnInit, OnDestroy {
  $element: HTMLElement;

  constructor(el: ElementRef) {
    this.$element = el.nativeElement;
  }

  fireMouseUp($event: MouseEvent) {
    $event.cancelBubble = true;
  }

  ngOnDestroy(): void {
    this.$element.removeEventListener('mousedown', this.fireMouseUp);
  }

  ngOnInit(): void {
    this.$element.addEventListener('mousedown', this.fireMouseUp);
  }

}

这里是 StackBlitz:https://stackblitz.com/edit/angular-tgrcni

这是 Github Angular 组件页面上的相关评论:https://github.com/angular/components/issues/13770#issuecomment-553193486

希望对您有所帮助。

这是我解决此问题的方法:

  1. 做一个布尔值来控制是否应该禁用cdkDrag。默认行为已禁用。
  2. mousedownmouseuptouchstarttouchend 事件处理程序添加到 cdkDragHandle 以切换控件。
  3. cdkDrag中,监听cdkDragReleased事件,使cdkDrag拖动后失效

副作用是使用您真正想要禁用的项目变得更加困难(例如,为那些真正禁用的项目应用样式)。

代码如下所示:

  • 组件class
  dragDisabled = true;
  • cdkDrag
<mat-row
  *matRowDef="let row; columns: displayedColumns"
  cdkDrag
  [cdkDragData]="row"
  [cdkDragDisabled]="dragDisabled"
  (cdkDragReleased)="dragDisabled = true"
></mat-row>
  • cdkDragHandle
<mat-icon
  cdkDragHandle
  (touchstart)="dragDisabled = false"
  (touchend)="dragDisabled = true"
  (mousedown)="dragDisabled = false"
  (mouseup)="dragDisabled = true"
  >drag_indicator</mat-icon
>

我在这里有一个替代答案(以前可能不可能)- 它结合了 @H Dog@Mamoon ur Rasheed 的答案。

根据 H Dog 的回答,将拖动手柄移动到单元格本身而不是行中,并使用 cdkDragRootElement 到 select 父垫行。但是,这仍然使整行保持可拖动状态。

接下来,默认禁用拖动,绑定到组件上的布尔值。当在拖动手柄上触发 mousedown 事件时,启用拖动,然后在下一帧中再次禁用它。

这会保留整行以允许正常交互,但允许通过具有适当占位符和预览功能的拖动手柄进行拖动。

这里是一个开始只按特定列拖动行的例子:

在组件上创建一个变量

dragEnabled = false;

将行设置为可拖动并通过变量禁用拖动

<mat-row *matRowDef="let row; columns: columns;" cdkDrag [cdkDragDisabled]="!dragEnabled">
</mat-row>

通过特定列上的鼠标事件控制 dragEnabled 变量状态

<ng-container matColumnDef="drag">
  <mat-header-cell *matHeaderCellDef>
  </mat-header-cell>
  <mat-cell *matCellDef="let entity"
            (mouseenter)="dragEnabled = true"
            (mouseleave)="dragEnabled = false">
    <mat-icon>
      drag_indicator
    </mat-icon>
  </mat-cell>
</ng-container>

现在您可以 select 行内容并将行拖动到特定列。