Drag-and-DropCSS

CSS verstehen
Anonymous
 Drag-and-Drop

Post by Anonymous »

Ich arbeite an einer dynamischen verschachtelten Baumstruktur mit angularem CDK -Drag & Drop. Die Drag-and-Drop-Funktion funktioniert auf der ersten Ebene, fällt jedoch fehl, wenn versucht wird, Elemente tiefer in der Hierarchie fallen zu lassen. Das CDKDropListDropped -Ereignis wird auf tieferen Ebenen nicht ausgelöst. (nach LV12)
Demo: https://nested-- angular.netlify.app
Codequelle: https: // GitHub. com/phamhung075/verschachtelter Baum
Wie kann ich sicherstellen, dass Drag & drop auf allen verschachtelten Ebenen funktioniert?
Vorschläge zum Beheben oder Debuggen dieses Problems?

Code: Select all

// tree.component.ts
import {
CdkDrag,
CdkDragDrop,
CdkDropList,
DragDropModule,
moveItemInArray,
} from '@angular/cdk/drag-drop';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { TreeNode } from '@shared/interfaces/tree-node.model';
import { TreeService } from '@shared/services/tree/tree.service';

@Component({
selector: 'app-tree',
standalone: true,
imports: [CommonModule, FormsModule, DragDropModule],
templateUrl: './branch-display.component.html',
styleUrls: ['./branch-display.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TreeComponent implements OnInit {
@Input() node!: TreeNode;
@Input() isLastChild = false;
@Input() isRoot = true;
@Input() dropListIds: string[] = [];
@Output() onDelete = new EventEmitter();
@Output() registerDropList = new EventEmitter();

dropListId = `drop-list-${Math.random().toString(36).substring(2)}`;
private hasBeenInitialized = false;

constructor(
private treeService: TreeService,
private changeDetectionRef: ChangeDetectorRef
) {}

ngOnInit() {
if (!this.hasBeenInitialized) {
this.registerDropList.emit(this.dropListId);
console.log('Tree Component Initialized:', {
nodeId: this.node.id,
value: this.node.value,
children: this.node.children.length,
});
this.hasBeenInitialized = true;
}
}

canDrop = (drag: CdkDrag, drop: CdkDropList) => {
const dragData = drag.data as TreeNode;
const dropData = this.node; // Current node where we're trying to drop

// Prevent dropping on itself or its descendants and root
if (
dragData.id === dropData.id ||
this.isDescendant(dragData, dropData) ||
dropData.id === 'root'
) {
return false;
}
return true;
};

private isDescendant(dragNode: TreeNode, targetNode: TreeNode): boolean {
return targetNode.children.some(
(child: TreeNode) =>
child.id === dragNode.id || this.isDescendant(dragNode, child)
);
}

drop(event: CdkDragDrop) {
const draggedNode = event.item.data as TreeNode;

if (event.previousContainer === event.container) {
// Moving within the same container
moveItemInArray(
event.container.data,
event.previousIndex,
event.currentIndex
);
} else {
// Moving to a different container
const success = this.treeService.moveNode(
draggedNode.id,
this.node.id,
'inside',
event.currentIndex // Pass the current index for position-based insertion
);

if (success) {
console.log('Node moved successfully to:', {
targetNode: this.node.value,
position: event.currentIndex,
});
}
}
}

moveUpLevel() {
const currentParent = this.treeService.getParentNode(this.node.id);
if (!currentParent) {
console.log('Cannot move up: No parent found');
return;
}

const grandParent = this.treeService.getParentNode(currentParent.id);
if (!grandParent) {
console.log('Cannot move up: No grandparent found');
return;
}

// Find the index where the current parent is in the grandparent's children
const parentIndex = grandParent.children.findIndex(
(child: TreeNode) =>  child.id === currentParent.id
);

if (parentIndex === -1) {
console.log('Cannot move up: Parent index not found');
return;
}

// Move the node one level up
const success = this.treeService.moveNode(
this.node.id,
grandParent.id,
'inside',
parentIndex + 1 // Insert after the current parent
);

if (success) {
console.log('Node moved up successfully:', {
nodeId: this.node.id,
newParentId: grandParent.id,
position: parentIndex + 1,
});
}
}

// Update the tree service to include better logging
removeChild(childId: string) {
const index = this.node.children.findIndex(
(child: TreeNode) => child.id === childId
);
if (index !== -1) {
const removedNode = this.node.children[index];
this.node.children.splice(index, 1);
console.log('Removed child:', {
childId,
parentId: this.node.id,
parentValue: this.node.value,
});
}
}

addChild() {
const newNode: TreeNode = {
id: Date.now().toString(),
value: 'New Node',
children: [],
};
this.treeService.updateNodeMaps(newNode, this.node.id);
this.node.children.push(newNode);
}

deleteNode() {
this.onDelete.emit(this.node.id);
}

onDragStarted() {
document.body.classList.add('dragging');
}

onDragEnded() {
document.body.classList.remove('dragging');
}

onRegisterDropList(childDropListId: string) {
if (!this.dropListIds.includes(childDropListId)) {
this.dropListIds.push(childDropListId);
this.registerDropList.emit(childDropListId);
}
}
}

< /code>
// tree.service.ts

import { Injectable } from '@angular/core';
import { TreeNode } from '@shared/interfaces/tree-node.model';

@Injectable({
providedIn: 'root',
})
export class TreeService {
private nodeMap = new Map();
private parentMap = new Map();

getRegisteredNodes(): string[] {
return Array.from(this.nodeMap.keys());
}

updateNodeMaps(node: TreeNode, parentId?: string) {
console.log('Updating node maps:', {
nodeId: node.id,
parentId,
nodeValue: node.value,
});

this.nodeMap.set(node.id, node);
if (parentId) {
this.parentMap.set(node.id, parentId);
}

console.log('Current maps after update:', {
nodeMapSize: this.nodeMap.size,
parentMapSize: this.parentMap.size,
nodeMapKeys: Array.from(this.nodeMap.keys()),
parentMapKeys: Array.from(this.parentMap.keys()),
});

node.children.forEach((child: TreeNode) =>
this.updateNodeMaps(child, node.id)
);
}

findNodeById(id: string): TreeNode | undefined {
const node = this.nodeMap.get(id);
console.log('Finding node by id:', {
searchId: id,
found: !!node,
nodeValue: node?.value,
});
return node;
}

getParentNode(nodeId: string): TreeNode | undefined {
const parentId = this.parentMap.get(nodeId);
const parentNode = parentId ? this.nodeMap.get(parentId) : undefined;
console.log('Getting parent node:', {
childId: nodeId,
parentId,
foundParent: !!parentNode,
parentValue: parentNode?.value,
});
return parentNode;
}

private isDescendant(nodeId: string, targetId: string): boolean {
console.log('Checking if descendant:', {
nodeId,
targetId,
});

let currentNode = this.findNodeById(targetId);
let depth = 0;

while (currentNode && depth <  1000) {
console.log('Traversing up the tree:', {
currentNodeId: currentNode.id,
currentNodeValue: currentNode.value,
depth,
});

if (currentNode.id === nodeId) {
console.log('Found ancestor match - would create circular reference');
return true;
}
currentNode = this.getParentNode(currentNode.id);
depth++;
}

console.log('No circular reference found');
return false;
}

moveNode(
nodeId: string,
targetId: string,
position: 'before' | 'after' | 'inside',
insertIndex?: number
): boolean {
console.log('Starting moveNode operation:', {
nodeId,
targetId,
position,
insertIndex,
targetNodeValue: this.findNodeById(targetId)?.value,
});

const sourceNode = this.findNodeById(nodeId);
const targetNode = this.findNodeById(targetId);

if (!sourceNode || !targetNode) {
console.log('Move failed: Source or target node not found');
return false;
}

const sourceParent = this.getParentNode(nodeId);
if (!sourceParent) {
console.log('Move failed: Source parent not found');
return false;
}

// Check for circular reference
if (this.isDescendant(nodeId, targetId)) {
console.log('Move failed: Would create circular reference');
return false;
}

// Remove from old parent
sourceParent.children = sourceParent.children.filter(
(child: TreeNode) => child.id !== nodeId
);

// Add to new location
if (position === 'inside') {
if (typeof insertIndex === 'number' && insertIndex >= 0) {
// Insert at specific position
targetNode.children.splice(insertIndex, 0, sourceNode);
} else {
// Default behavior: append to end
targetNode.children.push(sourceNode);
}
this.parentMap.set(nodeId, targetId);
} else {
const targetParent = this.getParentNode(targetId);
if (!targetParent) {
console.log('Move failed: Target parent not found');
return false;
}

const targetIndex = targetParent.children.findIndex(
(child: TreeNode) => child.id === targetId
);
const insertPosition =
position === 'after' ? targetIndex + 1 : targetIndex;
targetParent.children.splice(insertPosition, 0, sourceNode);
this.parentMap.set(nodeId, targetParent.id);
}

console.log('Move completed successfully.  New structure:', {
movedNodeId: nodeId,
newParentId: targetId,
newParentValue: targetNode.value,
insertPosition: insertIndex,
});

return true;
}
}

< /code>
// app-tree.component.ts (Parent component)
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnInit,
} from '@angular/core';
import { TreeComponent } from '../../components/branch-display/branch-display.component';
import { TreeNode } from '@shared/interfaces/tree-node.model';
import { TreeService } from '@shared/services/tree/tree.service';
import { mockTreeData } from '../../components/branch-display/mock-data';

@Component({
selector: 'app-tree-container',
standalone: true,
imports: [TreeComponent, CommonModule],
templateUrl: './branch-display-container.component.html',
styleUrls: ['./branch-display-container.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppTreeContainer implements OnInit {
treeData: TreeNode = mockTreeData;
dropListIds: string[] = [];
constructor(
private treeService: TreeService,
private changeDetectionRef: ChangeDetectorRef
) {}

ngOnInit() {
// Register all nodes in the tree with the service
this.registerNodesRecursively(this.treeData);
console.log('Tree Container Initialized');
}

private registerNodesRecursively(node: TreeNode, parentId?: string) {
// Register the current node
this.treeService.updateNodeMaps(node, parentId);
console.log('Registered node:', { nodeId: node.id, parentId });

// Register all children
node.children.forEach((child: TreeNode) => {
this.registerNodesRecursively(child, node.id);
});
}

onRegisterDropList(id: string) {
if (!this.dropListIds.includes(id)) {
this.dropListIds = [...this.dropListIds, id];
}
}
}

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post