I want to have the ability to drag and drop one of the rows that expand in the table to another set of rows that expand in the table. I have added drag and drop properties in the HTML for the expanded rows (expandedDetail) by using cdkDrag
, but that isn't working. I can drag an expanded row, but it just kind of sticks once I drop it instead of moving a row down or up like a normal drag and drop, if that makes sense. I am asking for assistance from anyone who is able to help me accomplish this. I am not sure what I'm missing from my code to be able to accomplish this.
HTML for the expanded rows that I want to drag and drop into other sets of expanded rows.
HTML
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
<ng-container matColumnDef="expandedDetail">
<td
mat-cell
*matCellDef="let element"
[attr.colspan]="columnsToDisplayWithExpand.length"
>
<div
class="example-element-detail"
[@detailExpand]="isElementExpanded(element) ? 'expanded' : 'collapsed'"
>
<div></div>
<!-- <div class="example-element-diagram">
<div class="example-element-position">{{element.position}}</div>
<div class="example-element-symbol">{{element.symbol}}</div>
<div class="example-element-name">{{element.name}}</div>
<div class="example-element-weight">{{element.weight}}</div>
</div>
<div class="example-element-description">
{{element.description}}
<span class="example-element-description-attribution">
-- Wikipedia
</span>
</div> -->
<div class="example-element-name expanded-column-spacing">
{{element.Name}}
</div>
<div class="example-element-weight expanded-column-spacing">
{{element.weight}}
</div>
<div class="example-element-weight expanded-column-spacing">
{{element.weight}}
</div>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
*matRowDef="let element; columns: displayedColumns;"
class="example-element-row"
[class.example-expanded-row]="isElementExpanded(element)"
></tr>
<tr
mat-row
*matRowDef="let row; columns: ['expandedDetail']"
(click)="toggle(row)"
cdkDrag
cdkDragLockAxis="y"
[cdkDragData]="row" ></tr>
<tr
mat-row
*matRowDef="let row; columns: ['expandedDetail']"
class="example-detail-row"
></tr>
<tr
mat-row
*matRowDef="let row; columns: ['expandedDetail']"
class="example-detail-row"
></tr>
<tr
mat-row
*matRowDef="let row; columns: ['expandedDetail']"
class="example-detail-row"
></tr>
</tr>
<tr mat-row *matRowDef="let row; columns: getDisplayedColumns()"></tr>
</table>
The code above is part of the StackBlitz link below, along with other files like the TypeScript and CSS files. I appreciate any assistance or guidance anyone has to get this to work. If there are other alternatives to use besides drag and drop, that would be helpful also.
In your StackBlitz, I see you hardcode the children by declaring multiple expandable details row.
<tr
mat-row
*matRowDef="let row; columns: ['expandedDetail']"
class="example-detail-row">
</tr>
Instead, you should remove those, and declare a children
property in the PeriodicElement
interface.
export interface PeriodicElement {
Name: string;
position: number;
weight: number;
symbol: string;
description: string;
children?: PeriodicElement[];
}
To preset the children with default item based on parent, you can achieve like this:
dataSource = new MatTableDataSource<PeriodicElement>(
ELEMENT_DATA.map((x) => ({ ...x, children: [x] }))
);
In your expandedDetail, you should have:
*ngFor
directive to render each child row.cdkDropList
directive together with the cdkDropListData
and cdkDropListDropped
event to allow dropping the element in the container.<ng-container matColumnDef="expandedDetail">
<td
mat-cell
*matCellDef="let element"
[attr.colspan]="columnsToDisplayWithExpand.length"
cdkDropList
[cdkDropListData]="{ parent: element, children: element.children }"
(cdkDropListDropped)="dropElement($event, element)"
>
<div
class="example-element-detail"
[@detailExpand]="isElementExpanded(element) ? 'expanded' : 'collapsed'"
*ngFor="let child of element.children; trackBy child"
cdkDrag
>
<div></div>
<div class="example-element-diagram">
<div class="example-element-position">{{child.position}}</div>
<div class="example-element-symbol">{{child.symbol}}</div>
<div class="example-element-name">{{child.name}}</div>
<div class="example-element-weight">{{child.weight}}</div>
</div>
<div class="example-element-description">
{{child.description}}
<span class="example-element-description-attribution">
-- Wikipedia
</span>
</div>
<div class="example-element-name expanded-column-spacing">
{{child.Name}}
</div>
<div class="example-element-weight expanded-column-spacing">
{{child.weight}}
</div>
<div class="example-element-weight expanded-column-spacing">
{{child.weight}}
</div>
</div>
</td>
</ng-container>
If you want the feature that allows drop the element without expand the parent, you can add the cdkDropList
directive as previous setup in <tr mat-row>
element.
<tr
mat-row
*matRowDef="let element; columns: displayedColumns;"
class="example-element-row"
[class.example-expanded-row]="isElementExpanded(element)"
cdkDropList
[cdkDropListData]="{ parent: element, children: element.children }"
(cdkDropListDropped)="dropElement($event, element)"
></tr>
In the dropElement
function, you have two sets of events:
You need to update the children
reference and call the this.dataSource._updateChangeSubscription()
so that the table will be updated.
dropElement(
event: CdkDragDrop<{
parent: PeriodicElement;
children: PeriodicElement[];
}>,
targetParent: PeriodicElement
) {
if (event.previousContainer === event.container) {
moveItemInArray(
targetParent.children,
event.previousIndex,
event.currentIndex
);
} else {
transferArrayItem(
event.previousContainer.data.children,
targetParent.children,
event.previousIndex,
event.currentIndex
);
}
event.previousContainer.data.parent.children = [
...event.previousContainer.data.children,
];
targetParent.children = [...targetParent.children];
this.dataSource._updateChangeSubscription();
}