Dragging and Dropping

The drag-and-drop functionality enables the user to move the TreeView items by dragging and dropping them within a single tree or across multiple trees.

Basics

To implement dragging and dropping in the TreeView:

  1. Set the draggable property to true.
  2. Handle the dispatched drag events.

To facilitate the event handling, the TreeView provides the following utilities:

Moving Items within a Single Tree

The following example demonstrates how to:

  • Implement the drag-and-drop functionality within a single TreeView by using all drag-and-drop utilities.
  • Update the expanded and selected item fields by using the processTreeViewItems function.
Example
View Source
Change Theme:

Moving Items across Multiple Trees

The following example demonstrates how to:

  • Implement the drag-and-drop functionality across two TreeViews by using all drag-and-drop utilities.
  • Directly update the expanded and selected item fields.
    class App extends React.Component{
        treeView1Guid;
        treeView2Guid;
        dragClue;
        dragOverCnt = 0;
        isDragDrop = false;

        state = { tree, tree2 };

        render() {
            return (
                <div>
                    <TreeView data={this.state.tree}
                        draggable={true} onItemDragOver={this.onItemDragOver} onItemDragEnd={this.onItemDragEnd}
                        ref={treeView => this.treeView1Guid = treeView && treeView.guid}
                        expandIcons={true} onExpandChange={this.onExpandChange} onItemClick={this.onItemClick}
                    />
                    <TreeView data={this.state.tree2}
                        draggable={true} onItemDragOver={this.onItemDragOver} onItemDragEnd={this.onItemDragEnd}
                        ref={treeView => this.treeView2Guid = treeView && treeView.guid}
                        expandIcons={true} onExpandChange={this.onExpandChange} onItemClick={this.onItemClick}
                    />
                    <TreeViewDragClue ref={dragClue => this.dragClue = dragClue} />
                </div>
            );
        }

        onItemDragOver = (event) => {
            this.dragOverCnt++;
            this.dragClue.show(event.pageY + 10, event.pageX, event.item.text, this.getClueClassName(event));
        }
        onItemDragEnd = (event) => {
            this.isDragDrop = this.dragOverCnt > 0;
            this.dragOverCnt = 0;
            this.dragClue.hide();

            const eventAnalyzer = new TreeViewDragAnalyzer(event).init();

            if (eventAnalyzer.isDropAllowed) {
                const { sourceData, targetData } = moveTreeViewItem(
                    event.itemHierarchicalIndex,
                    this.resolveData(event.target.guid),
                    eventAnalyzer.getDropOperation(),
                    eventAnalyzer.destinationMeta.itemHierarchicalIndex,
                    this.resolveData(eventAnalyzer.destinationMeta.treeViewGuid)
                );

                this.setState({
                    [this.resolveDataKey(event.target.guid)]: sourceData,
                    [this.resolveDataKey(eventAnalyzer.destinationMeta.treeViewGuid)]: targetData
                });
            }
        }
        onItemClick = (event) => {
            if (!this.isDragDrop) {
                event.item.selected = !event.item.selected;
                this.forceUpdate();
            }
        }
        onExpandChange = (event) => {
            event.item.expanded = !event.item.expanded;
            this.forceUpdate();
        }

        getClueClassName(event) {
            const eventAnalyzer = new TreeViewDragAnalyzer(event).init();
            const { itemHierarchicalIndex: itemIndex, treeViewGuid } = eventAnalyzer.destinationMeta;

            if (eventAnalyzer.isDropAllowed) {
                switch (eventAnalyzer.getDropOperation()) {
                    case 'child':
                        return 'k-i-plus';
                    case 'before':
                        return itemIndex === '0' || itemIndex.endsWith(`${SEPARATOR}0`) ?
                            'k-i-insert-up' : 'k-i-insert-middle';
                    case 'after':
                        const siblings = getSiblings(itemIndex, this.resolveData(treeViewGuid));
                        const lastIndex = Number(itemIndex.split(SEPARATOR).pop());

                        return lastIndex < siblings.length - 1 ? 'k-i-insert-middle' : 'k-i-insert-down';
                    default:
                        break;
                }
            }

            return 'k-i-cancel';
        }
        resolveData(treeViewGuid) {
            return treeViewGuid === this.treeView1Guid ? this.state.tree : this.state.tree2;
        }
        resolveDataKey(treeViewGuid) {
            return treeViewGuid === this.treeView1Guid ? 'tree' : 'tree2';
        }
    }

    function getSiblings(itemIndex, data) {
        let result = data;

        const indices = itemIndex.split(SEPARATOR).map(index => Number(index));
        for (let i = 0; i < indices.length - 1; i++) {
            result = result[indices[i]].items;
        }

        return result;
    }

    const SEPARATOR = '_';
    const tree = [{
        text: 'Furniture', expanded: true, items: [
            { text: 'Tables & Chairs' }, { text: 'Sofas' }, { text: 'Occasional Furniture' }]
    }, {
        text: 'Decor', expanded: true, items: [
            { text: 'Bed Linen' }, { text: 'Curtains & Blinds' }, { text: 'Carpets' }]
    }];

    const tree2 = [{
        text: 'Storage', expanded: true, items: [
            { text: 'Wall Shelving' }, { text: 'Floor Shelving' }, { text: 'Kids Storage' }]
    }, {
        text: 'Lights', expanded: true, items: [
            { text: 'Ceiling' }, { text: 'Table' }, { text: 'Floor' }]
    }];

    ReactDOM.render(
        <App />,
        document.querySelector('my-app')
    );