This question has been brought up a number of times on SO, but not recently, and the example here is more complete than other ones I have seen. The best implemention of drag and drop with a material tree is this one posted here on medium by @inorganik.
I have cloned the StackBlitz here.
However, my issue is that you can only move nodes if you have at least one child element on a node. For example, if you remove all the child nodes from Application and then try to add it back to Application.
The dropped node will only be inserted into the parent if the node has siblings, at line 232:
// insert node
const newSiblings = findNodeSiblings(changedData, nodeAtDest.id);
if (!newSiblings) return;
newSiblings.splice(relativeIndex, 0, nodeToInsert);
It seems logical to me that the solution would be to track if the user was hovering the moved node over another node when they dropped it, and then assume that the user wants to make the hovered node the parent, please see the screen grab:
My question is: is this the best way to go about solving this problem? If so, how do track the hovered node using the Angular Drag Drop CDK?
UPDATE:
I have gotten this functionality partially working. I've updated the StackBlitz here.
I updated the dragHover
method based on other implementations of mat-tree DnD I've found which sets insert mode based on how far to the right the user drags the hovered node:
dragHover(node: FileFlatNode) {
// untype the event
const newEvent: any = event;
const percentageX = newEvent.offsetX / newEvent.target.clientWidth;
if (percentageX > .25) {
this.dragNodeInsertToParent = true;
} else {
this.dragNodeInsertToParent = false;
}
if (this.dragging) {
clearTimeout(this.expandTimeout);
this.expandTimeout = setTimeout(() => {
this.treeControl.expand(node);
}, this.expandDelay);
}
}
I added this to the drop
method. If dragNodeInsertToParent
is true
,
find the index of the node at destination of the drop, and push the node to insert into its children array.
if (this.dragNodeInsertToParent) {
const indexOfParent = newSiblings.findIndex( element => element.id === nodeAtDest.id);
newSiblings[indexOfParent].children.push(nodeToInsert);
} else {
newSiblings.splice(relativeIndex, 0, nodeToInsert);
}
Now, if you pull the node out far enough to the right, insert mode is triggered, and you can add the child back to an empty parent node:
Anyway, still working on this, if anyone has any ideas for making this work more intuitively, please leave a comment.