Guides

Drag & Drop

Demo
Options
Event
Override default behaviour
Drag a node outside of the tree
Drag an external element into the tree
Drag to an empty tree
Mobile
Styling

Demo

Options

options = {
  allowDrag: (node) => node.isLeaf,
  allowDrop: (element, { parent, index }) {
    // return true / false based on element, to.parent, to.index. e.g.
    return parent.hasChildren;
  },
  getNodeClone: (node) => ({
    ...node.data,
    id: uuid.v4(),
    name: `copy of ${node.data.name}`
  })
};

options2 = {
  allowDrag: true,
  allowDrop: false
};

<tree-root
		[nodes]="nodes"
		[options]="options"></tree-root>

Dragging:
Enable dragging with allowDrag option (default false) accepts either a boolean or a function:

  • Boolean value - decides if drop is allowed or not on the tree
  • Function - decides on a per node basis if drop is allowed. The function receives:
    • node - the dragged TreeNode

Dropping:
Enable selective dropping with the allowDrop option (default true) accepts either a boolean or a function:

  • Boolean value - decides if drop is allowed or not on the tree
  • Function - decides on a per node basis if drop is allowed. The function receives:
    • element - the dragged element
    • to - drop location structure
      • parent - the parent node
      • index - the index inside the parent's children where the element is dropped

Copying:
The default behaviour of the tree is to copy the node when ctrl is pressed while dropping the node

  • By default the tree will shallow-clone the node data and generates a random ID
  • if getNodeClone option is supplied, it will be called to get a copy of the node. It receives a TreeNode object, and should return a node object (only the data).

Event

You can listen to moveNode events and get the moved node, and its new location in the tree

options = {
  allowDrag: true
}

onMoveNode($event) {
  console.log(
  	"Moved",
  	$event.node.name,
  	"to",
  	$event.to.parent.name,
  	"at index",
   	$event.to.index);
}

<tree-root
		(moveNode)="onMoveNode($event)"
		[nodes]="nodes"
		[options]="options"></tree-root>

Override default behaviour

use actionMapping.mouse.drop to override the default drag behaviour.
You can also listen to all other drag events like dragEnd, dragLeave etc.:

options = {
  allowDrag: true,
  actionMapping: {
  	mouse: {
      drop: (tree:TreeModel, node:TreeNode, $event:any, {from, to}) => {
        // use from to get the dragged node.
        // use to.parent and to.index to get the drop location
        // use TREE_ACTIONS.MOVE_NODE to invoke the original action
      },
      dragStart?: IActionHandler,
      drag?: IActionHandler,
      dragEnd?: IActionHandler,
      dragOver?: IActionHandler,
      dragLeave?: IActionHandler,
      dragEnter?: IActionHandler      
    }
  }
}

<tree-root
		[nodes]="nodes"
		[options]="options"></tree-root>

In the drop callback, you get a 'from' and 'to' objects:

  • from:

    • If dragging a node, then from === the dragged node
    • If dragging something else, it is the draggedElement (see treeDrag directive below)
  • to:

    • parent: the parent node
    • index: the index inside the parent's children where the node should be dropped
    • dropOnNode: distinguish between dropping on the node itself or the drop slot

Drag and drop between trees

This is enabled by default when dragging is enabled

Drag a node outside of the tree

You can use the (treeDrop) directive to allow dragging nodes to an external element.
For example:

<div (treeDrop)="onDrop($event)"
     [treeAllowDrop]="allowDrop.bind(this)"></div>

  onDrop($event) {
    // Dropped $event.element
  }

  allowDrop(element) {
    // Return true/false based on element
  }

Use $event.element inside the callback.
Use [treeAllowDrop] Input to specify a function that runs onDragOver, and decides if the dropping is allowed or not.

Drag an external element into the tree

You can use the [treeDrag] directive to allow dragging external elements into the tree.
Then use a custom action to handle the drop (see Action Mapping section).

For example:

options = {
  actionMapping: {
    mouse: {
      drop: (tree, node, $event, {from, to}) => {
        console.log('drag', from, to); // from === {name: 'first'}
        // Add a node to `to.parent` at `to.index` based on the data in `from`
        // Then call tree.update()
      }
    }
  }
}

<p [treeDrag]="{name: 'first'}" [treeDragEnabled]="true">Drag me!</p>
<p [treeDrag]="{name: 'second'}">Drag me as well!</p>

The data that you pass to [treeDrag] will be passed to the handler in the from parameter.

Use to.parent and to.index to get the drop location.
Use to.dropOnNode to distinguish between dropping on the node itself or the drop slot.
If you add a new node to the tree, you'll need to call tree.update() afterwards.

Use [treeDragEnabled] boolean Input to decide if the drag is enabled or not.

Drag to an empty tree

To drag to an empty tree, set your nodes to an empty array.
If nodes is undefined or null, the drop slot will not appear.

Mobile

To support drag and drop on mobile, you can use DragDropTouch polyfill in your project to enable it.
Download the code from here: https://github.com/Bernardo-Castilho/dragdroptouch
Place it somewhere in your code, and import it from polyfills.ts, or just place a script tag to include it.

Styling

The following classes are available for dragging over elements, based on allowDrop:

  • is-dragging-over
  • is-dragging-over-disabled

You can disable those classes completely by setting the allowDragoverStyling option to false.

Performance Issues

It there are performance issues on large trees while drag and drop it's possible to detach change detection during the drag.

// use private cdr: ChangeDetectorRef in constructor

let treeOptions: ITreeOptions = {
    actionMapping: {
        mouse: {
                dragStart: () => { this.cdr.detach(); },
                dragEnd: () => { this.cdr.reattach(); },
            }
        }
};