Your search did not match any results.

Drag & Drop for Plain Data Structure

This demo shows how to enable node drag and drop in a TreeView with a plain data structure. You can reorder nodes within a single tree view or drag and drop them between two separate tree views.

Use Sortable to implement drag and drop functionality. The following steps describe how to configure this widget to work with the TreeView:

  1. Allow users to reorder nodes
    Wrap the TreeView in a Sortable and enable the Sortable's allowReordering option.

  2. Allow users to change node hierarchy
    Enable the allowDropInsideItem option so that users can drop one node into another, which adds it as the target node's child. If this option is disabled, users can only drop nodes in between other nodes.

  3. Allow users to drag only tree view nodes
    To specify tree view nodes as drag targets, set the filter option to a class selector. Since all tree view nodes use the dx-treeview-node class, you can use this class' selector.

  4. Prevent a node from being moved into its child node
    When a user moves a parent node into its own child node, it breaks the hierarchy. To prevent this situation, implement the onDragChange function in which you must traverse up the node tree. If the target is a child of the currenty dragged node, cancel the ability to drop the node.

  5. Reorder nodes in code
    Implement the onDragEnd function. In this function, you should gather information about the nodes that moved. Then, reorder the nodes in the data source (see the moveNode function), and reassign the data source to the TreeView's items option.

  6. Specify tree view identifiers (for drag and drop between multiple tree views only)
    Identifiers help distinguish between multiple tree views. Save them in the Sortable's data option. The tree views below have the following identifiers: "driveC" and "driveD".

  7. Combine tree views into one drag and drop group (for drag and drop between multiple tree views only)
    Set the Sortable's group option to the same value for all tree views. This allows users to move nodes between the tree views.

<div class="form"> <div class="drive-panel"> <div><i class="icon dx-icon-activefolder"></i>&nbsp;Drive C:</div> @(Html.DevExtreme().Sortable() .Filter(".dx-treeview-item") .Data("driveC") .Group("shared") .AllowReordering(true) .AllowDropInsideItem(true) .OnDragChange("onDragChange") .OnDragEnd("onDragEnd") .Content( Html.DevExtreme().TreeView() .ID("treeviewDriveC") .ExpandNodesRecursive(false) .DataStructure(TreeViewDataStructure.Plain) .KeyExpr("Id") .ParentIdExpr("ParentId") .ItemTemplate(@<text> <div> <i class="icon dx-icon-<%- IsDirectory ? 'activefolder' : 'file' %>"></i><%-Name %> </div> </text>) .DataSource(d => d.Mvc().LoadAction("GetPlainDataForDragAndDrop")) .Width(250) .Height(380).ToString() )) </div> <div class="drive-panel"> <div><i class="icon dx-icon-activefolder"></i>&nbsp;Drive D:</div> @(Html.DevExtreme().Sortable() .Filter(".dx-treeview-item") .Data("driveD") .Group("shared") .AllowReordering(true) .AllowDropInsideItem(true) .OnDragChange("onDragChange") .OnDragEnd("onDragEnd") .Content( Html.DevExtreme().TreeView() .ID("treeviewDriveD") .ExpandNodesRecursive(false) .DataStructure(TreeViewDataStructure.Plain) .KeyExpr("Id") .ParentIdExpr("ParentId") .ItemTemplate(@<text> <div> <i class="icon dx-icon-<%- IsDirectory ? 'activefolder' : 'file' %>"></i><%-Name %> </div> </text>) .Width(250) .Height(380).ToString() ) ) </div> </div> <script> function onDragChange(e) { if(e.fromComponent === e.toComponent) { var $nodes = e.element.find(".dx-treeview-node"); var isDragIntoChild = $nodes.eq(e.fromIndex).find($nodes.eq(e.toIndex)).length > 0; if(isDragIntoChild) { e.cancel = true; } } } function onDragEnd(e) { if(e.fromComponent === e.toComponent && e.fromIndex === e.toIndex) { return; } var fromTreeView = getTreeView(e.fromData); var toTreeView = getTreeView(e.toData); var fromNode = findNode(fromTreeView, e.fromIndex); var toNode = findNode(toTreeView, calculateToIndex(e)); if(e.dropInsideItem && toNode !== null && !toNode.itemData.IsDirectory) { return; } var fromTopVisibleItemId = getTopVisibleNodeId(fromTreeView); var toTopVisibleItemId = getTopVisibleNodeId(toTreeView); var fromItems = fromTreeView.option('items'); var toItems = toTreeView.option('items'); moveNode(fromNode, toNode, fromItems, toItems, e.dropInsideItem); fromTreeView.option("items", fromItems); toTreeView.option("items", toItems); fromTreeView.scrollToItem(fromTopVisibleItemId); toTreeView.scrollToItem(toTopVisibleItemId); } function getTreeView(driveName) { return driveName === 'driveC' ? $('#treeviewDriveC').dxTreeView('instance') : $('#treeviewDriveD').dxTreeView('instance'); } function calculateToIndex(e) { if(e.fromComponent != e.toComponent || e.dropInsideItem) { return e.toIndex; } return e.fromIndex >= e.toIndex ? e.toIndex : e.toIndex + 1; } function findNode(treeView, index) { var $nodes = treeView.element().find(".dx-treeview-node"); if($nodes.length <= index) { return null; } var id = $nodes.eq(index).attr("data-item-id"); return findNodeById(treeView.getNodes(), id); } function findNodeById(nodes, id) { for(var i = 0; i < nodes.length; i++) { if (nodes[i].itemData.Id == id) { return nodes[i]; } else { var node = findNodeById(nodes[i].children, id); if(node !== null) { return node; } } } return null; } function moveNode(fromNode, toNode, fromItems, toItems, isDropInsideItem) { var fromIndex = findIndex(fromItems, fromNode.itemData.Id); fromItems.splice(fromIndex, 1); var toIndex = toNode === null || isDropInsideItem ? toItems.length : findIndex(toItems, toNode.itemData.Id); toItems.splice(toIndex, 0, fromNode.itemData); moveChildren(fromNode, fromItems, toItems); if(isDropInsideItem) { fromNode.itemData.ParentId = toNode.itemData.Id; } else { fromNode.itemData.ParentId = toNode != null ? toNode.itemData.ParentId : undefined; } } function moveChildren(node, fromItems, toItems) { if(!node.itemData.IsDirectory) { return; } node.children.forEach(function(child) { if(child.itemData.IsDirectory) { moveChildren(child, fromItems, toItems); } var fromIndex = findIndex(fromItems, child.itemData.Id); fromItems.splice(fromIndex, 1); toItems.splice(toItems.length, 0, child.itemData); }); } function findIndex(array, id) { var idsArray = array.map(function(elem) { return elem.Id; }); return idsArray.indexOf(id); } function getTopVisibleNodeId(component) { var treeViewElement = component.element().get(0); var treeViewTopPosition = treeViewElement.getBoundingClientRect().top; var nodes = treeViewElement.querySelectorAll(".dx-treeview-node"); for(var i = 0; i < nodes.length; i++) { var nodeTopPosition = nodes[i].getBoundingClientRect().top; if(nodeTopPosition >= treeViewTopPosition) { return nodes[i].getAttribute('data-item-id'); } } return null; } </script>
using DevExtreme.AspNet.Data; using DevExtreme.AspNet.Mvc; using DevExtreme.NETCore.Demos.Models; using DevExtreme.NETCore.Demos.Models.SampleData; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using System.Collections.Generic; using System.Linq; namespace DevExtreme.NETCore.Demos.Controllers { public class TreeViewController : Controller { public ActionResult DragAndDropPlainDataStructure() { return View(); } public object GetPlainDataForDragAndDrop(DataSourceLoadOptions loadOptions) { return Content(JsonConvert.SerializeObject(DataSourceLoader.Load(TreeViewPlainDataForDragAndDrop.FileSystemItems, loadOptions)), "application/json"); } } }
using System.Collections.Generic; namespace DevExtreme.NETCore.Demos.Models.TreeView { public class FileSystemItem { public string Id { get; set; } public string ParentId { get; set; } public string Name { get; set; } public bool IsDirectory { get; set; } public IEnumerable<FileSystemItem> Items { get; set; } } }
using System.Collections.Generic; using DevExtreme.NETCore.Demos.Models.TreeView; namespace DevExtreme.NETCore.Demos.Models.SampleData { public static class TreeViewPlainDataForDragAndDrop { public static readonly IEnumerable<FileSystemItem> FileSystemItems = new[] { new FileSystemItem { Id = "1", Name = "Documents", IsDirectory= true, }, new FileSystemItem { Id = "2", ParentId = "1", Name = "Projects", IsDirectory= true }, new FileSystemItem { Id = "3", ParentId = "2", Name = "About.rtf", IsDirectory= false }, new FileSystemItem { Id = "4", ParentId = "2", Name = "Passwords.rtf", IsDirectory= false }, new FileSystemItem { Id = "5", ParentId = "2", Name = "About.xml", IsDirectory= false }, new FileSystemItem { Id = "6", ParentId = "1", Name = "Managers.rtf", IsDirectory= false }, new FileSystemItem { Id = "7", ParentId = "2", Name = "ToDo.txt", IsDirectory= false }, new FileSystemItem { Id = "8", Name = "Images", IsDirectory= true, }, new FileSystemItem { Id = "9", ParentId = "8", Name = "logo.png", IsDirectory= false }, new FileSystemItem { Id = "10", ParentId = "8", Name = "banner.gif", IsDirectory= false }, new FileSystemItem { Id = "11", Name = "System", IsDirectory= true, }, new FileSystemItem { Id = "12", ParentId = "11", Name = "Employees.txt", IsDirectory= false }, new FileSystemItem { Id = "13", ParentId = "11", Name = "PasswordList.txt", IsDirectory= false, }, new FileSystemItem { Id = "14", Name = "Description.rtf", IsDirectory= false }, new FileSystemItem { Id = "15", Name = "Description.txt", IsDirectory= false } }; } }
.form { display: flex; } .form>div { display: inline-block; vertical-align: top; } #treeviewDriveC, #treeviewDriveD { margin-top: 10px; } .drive-panel { padding: 20px 30px; font-size: 115%; font-weight: bold; border: 1px solid #dadada; height: 100%; } .dx-treeview-item .icon { position: relative; left: -4px; top: 1px; } .drive-panel:last-of-type { border-left-width: 0px; }