Ext JS Tree Panels — Hierarchical Data, Nodes, and Drag-and-Drop
In this tutorial, you will learn about Ext JS Tree Panels. We cover key concepts, practical examples, and best practices to help you master this topic.
Ext JS Tree panel renders hierarchical data in an expandable node structure, supporting Lazy Loading, drag-and-drop, checkbox selection, and integration with grids and forms.
What You'll Learn
- TreeStore and node configuration
- Lazy loading async nodes
- Drag-and-drop between trees
- Checkbox selection and filtering
- Tree context menus and events
Why It Matters
Hierarchical data — file systems, org charts, category trees, navigation menus — appears in most business applications. Building expand/collapse, lazy loading, and drag reordering from scratch is error-prone. Ext JS Tree handles it all.
Real-World Use
A file explorer interface with a tree panel for folder navigation, lazy loading subdirectories, drag-and-drop to move files between folders, right-click context menus for rename and delete, and a grid showing the selected folder's contents.
Tree Architecture
flowchart TD
A[Tree Panel] --> B[TreeStore]
B --> C[Root Node]
C --> D[Child Nodes]
C --> E[Lazy Load]
D --> F[Leaf Node]
D --> G[Branch Node]
A --> H[Selection Model]
A --> I[Drag-Drop Plugin]
A --> J[Events]
J --> K[beforeload]
J --> L[itemclick]
J --> M[checkchange]
style A fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px
Basic Tree Configuration
Ext.create('Ext.tree.Panel', {
title: 'Simple Tree',
width: 300,
height: 400,
store: {
type: 'tree',
root: {
text: 'Root',
expanded: true,
children: [{
text: 'Documents',
expanded: true,
children: [
{ text: 'resume.pdf', leaf: true, size: '2.4 MB' },
{ text: 'cover-letter.docx', leaf: true, size: '1.1 MB' }
]
}, {
text: 'Images',
expanded: false,
children: [
{ text: 'logo.png', leaf: true, size: '45 KB' },
{ text: 'banner.jpg', leaf: true, size: '120 KB' },
{ text: 'Screenshots', expanded: false, children: [
{ text: 'dashboard.png', leaf: true, size: '89 KB' },
{ text: 'settings.png', leaf: true, size: '67 KB' }
]}
]
}, {
text: 'Downloads',
leaf: true
}]
}
},
columns: [{
xtype: 'treecolumn',
text: 'Name',
dataIndex: 'text',
flex: 2
}, {
text: 'Size',
dataIndex: 'size',
width: 100
}],
renderTo: Ext.getBody()
});
Expected output: A tree with expandable folders and leaf files. The treecolumn renders the expand/collapse icons. Clicking arrows expands or collapses branches.
TreeStore with Lazy Loading
Ext.create('Ext.tree.Panel', {
title: 'Lazy Load Tree',
width: 350,
height: 450,
store: {
type: 'tree',
root: {
text: 'Categories',
expanded: true,
id: 'root'
},
proxy: {
type: 'ajax',
url: '/api/categories',
reader: { type: 'json' }
},
// The API sends children only when a node is expanded
// Node ID is passed as 'node' parameter
nodeParameter: 'id',
lazyLoad: true
},
columns: [{
xtype: 'treecolumn',
text: 'Category Name',
dataIndex: 'text',
flex: 1
}, {
text: 'Product Count',
dataIndex: 'count',
width: 120
}]
});
// Server response format for a node:
// [
// { id: 101, text: 'Electronics', count: 42, leaf: false },
// { id: 102, text: 'Clothing', count: 87, leaf: false },
// { id: 103, text: 'Books', count: 15, leaf: true }
// ]
Expected output: The tree loads only the root initially. When a non-leaf node is expanded, it sends an Ajax request with the node ID and loads only that level's children.
Node Manipulation
var tree = Ext.create('Ext.tree.Panel', {
title: 'Dynamic Tree',
width: 300,
height: 400,
store: {
type: 'tree',
root: { text: 'Root', expanded: true, children: [] }
},
renderTo: Ext.getBody()
});
var root = tree.getRootNode();
// Add nodes
var folder1 = root.appendChild({ text: 'Folder 1', leaf: false });
folder1.appendChild({ text: 'File 1.txt', leaf: true });
folder1.appendChild({ text: 'File 2.txt', leaf: true });
var folder2 = root.insertChild(0, { text: 'Folder 2 (top)', leaf: false });
// Navigate
var node = tree.getStore().getNodeById('someId');
// Remove
folder1.removeChild(folder1.findChild('text', 'File 1.txt'));
// Find and update
var file2 = folder1.findChild('text', 'File 2.txt');
if (file2) {
file2.set('text', 'File 2-renamed.txt');
file2.set('size', '2.1 MB');
}
// Expand all
root.expand(true);
// Collapse all
root.collapse(true);
Expected output: Nodes are added, inserted, removed, renamed and expanded programmatically. The tree reflects changes immediately.
Drag-and-Drop
Ext.create('Ext.tree.Panel', {
title: 'Drag Source',
width: 300,
height: 400,
store: {
type: 'tree',
root: {
text: 'Available Items',
expanded: true,
children: [
{ text: 'Item A', leaf: true },
{ text: 'Item B', leaf: true },
{ text: 'Group 1', expanded: true, children: [
{ text: 'Item C', leaf: true },
{ text: 'Item D', leaf: true }
]}
]
}
},
viewConfig: {
plugins: {
ptype: 'treeviewdragdrop',
dragText: 'Drag item to reorder',
ddGroup: 'itemsDD',
allowContainerDrops: true,
appendOnly: false,
sortOnDrop: true,
containerScroll: true
}
},
renderTo: Ext.getBody()
});
Expected output: Nodes can be dragged to reposition within the tree or moved between different branches. The drag text shows the node name while dragging.
Checkbox Selection
Ext.create('Ext.tree.Panel', {
title: 'Checkbox Tree',
width: 350,
height: 400,
store: {
type: 'tree',
root: {
text: 'Permissions',
expanded: true,
children: [{
text: 'Admin', expanded: true,
children: [
{ text: 'Create Users', leaf: true, checked: false },
{ text: 'Delete Users', leaf: true, checked: false },
{ text: 'Edit Settings', leaf: true, checked: true }
]
}, {
text: 'Editor', expanded: true,
children: [
{ text: 'Create Posts', leaf: true, checked: true },
{ text: 'Edit Posts', leaf: true, checked: true },
{ text: 'Delete Posts', leaf: true, checked: false }
]
}]
}
},
// Enable checkboxes
rootVisible: false,
// The 'checked' property enables the checkbox
listeners: {
checkchange: function(node, checked) {
console.log(node.get('text') + ' is now ' + (checked ? 'checked' : 'unchecked'));
// Cascade to children
node.cascadeBy(function(child) {
child.set('checked', checked);
});
}
}
});
Expected output: Each tree node shows a checkbox. Checking a parent automatically checks all children. The checkchange event logs every toggle.
Context Menu
var tree = Ext.create('Ext.tree.Panel', {
title: 'Tree with Context Menu',
width: 350,
height: 400,
store: { /* store config */ },
listeners: {
itemcontextmenu: function(view, record, item, index, event) {
event.stopEvent();
var contextMenu = Ext.create('Ext.menu.Menu', {
items: [{
text: 'Rename',
iconCls: 'x-fa fa-pencil',
handler: function() { console.log('Rename:', record.get('text')); }
}, {
text: 'Delete',
iconCls: 'x-fa fa-trash',
handler: function() { record.remove(); }
}, '-', {
text: 'Add Child',
iconCls: 'x-fa fa-plus-circle',
handler: function() {
record.appendChild({ text: 'New Node', leaf: true });
record.expand();
}
}, {
text: 'Properties',
iconCls: 'x-fa fa-info-circle',
handler: function() { console.log('Properties:', record.getData()); }
}]
});
contextMenu.showAt(event.getXY());
}
}
});
Expected output: Right-clicking a tree node opens a context menu with Rename, Delete, Add Child, and Properties options.
Common Mistakes
Not setting leaf: true on leaf nodes - Nodes without children but leaf: false show expand icons that load nothing when clicked. Always set leaf: true on nodes with no children.
Forgetting to call expand() after appendChild - New child nodes inside a collapsed parent are invisible until the parent expands. Call parent.expand() after adding children.
Confusing TreeStore with regular Store - TreeStore works with hierarchical data and uses root/children structure. A regular Store flattens data into rows and cannot render tree structures.
Not using the treecolumn xtype - The treecolumn xtype renders the expand/collapse icons and indentation. Using a regular column breaks the tree visual hierarchy.
Ignoring lazy load server response format - The server must return an array of node objects with id, text, leaf, and optionally children. Non-leaf nodes trigger additional requests when expanded.
Practice Questions
- What is the difference between a node with leaf: true and leaf: false?
- How does lazy loading work in a TreeStore?
- How do you enable drag-and-drop between two different tree panels?
- How do you get all checked nodes in a checkbox tree?
- What event fires when a tree node is expanded?
Challenge: Build a file manager with a dual-panel layout: left tree (folder navigation with lazy loading), right grid (selected folder's files), context menu on tree nodes (new folder, rename, delete), drag-and-drop to move files between folders, and checkbox selection for batch operations.
FAQ
Mini Project
Build a category management tree with: lazy loading from a server API, drag-and-drop to reorganize categories, context menu for add/edit/delete, checkbox selection to assign products to multiple categories, and a side panel showing the selected category's details with an inline edit form.
What's Next
Trees organize navigation. Learn how Ext JS Tab Panels manage multiple content panels in a single container with tab switching and dynamic tab creation.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro