New to KendoReactStart a free 30-day trial

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.
Change Theme
Theme
Loading ...

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.
jsx
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'));