16 KiB
TreeView Component
Introduction
The TreeView component provides an interactive hierarchical data visualization with full CRUD operations. It's designed for displaying tree-structured data like file systems, organizational charts, or navigation menus with inline editing capabilities.
Key features:
- Expand/collapse nodes with visual indicators
- Add child and sibling nodes dynamically
- Inline rename with keyboard support (ESC to cancel)
- Delete nodes (only leaf nodes without children)
- Node selection tracking
- Persistent state per session
- Configurable icons per node type
Common use cases:
- File/folder browser
- Category/subcategory management
- Organizational hierarchy viewer
- Navigation menu builder
- Document outline editor
Quick Start
Here's a minimal example showing a file system tree:
from fasthtml.common import *
from myfasthtml.controls.TreeView import TreeView, TreeNode
# Create TreeView instance
tree = TreeView(parent=root_instance, _id="file-tree")
# Add root folder
root = TreeNode(id="root", label="Documents", type="folder")
tree.add_node(root)
# Add some files
file1 = TreeNode(id="file1", label="report.pdf", type="file")
file2 = TreeNode(id="file2", label="budget.xlsx", type="file")
tree.add_node(file1, parent_id="root")
tree.add_node(file2, parent_id="root")
# Expand root to show children
tree.expand_all()
# Render the tree
return tree
This creates an interactive tree where users can:
- Click chevrons to expand/collapse folders
- Click labels to select items
- Use action buttons (visible on hover) to add, rename, or delete nodes
Note: All interactions use commands and update via HTMX without page reload.
Basic Usage
Creating a TreeView
TreeView is a MultipleInstance, allowing multiple trees per session. Create it with a parent instance:
tree = TreeView(parent=root_instance, _id="my-tree")
TreeNode Structure
Nodes are represented by the TreeNode dataclass:
from myfasthtml.controls.TreeView import TreeNode
node = TreeNode(
id="unique-id", # Auto-generated UUID if not provided
label="Node Label", # Display text
type="default", # Type for icon mapping
parent=None, # Parent node ID (None for root)
children=[] # List of child node IDs
)
Adding Nodes
Add nodes using the add_node() method:
# Add root node
root = TreeNode(id="root", label="Root", type="folder")
tree.add_node(root)
# Add child node
child = TreeNode(label="Child 1", type="item")
tree.add_node(child, parent_id="root")
# Add with specific position
sibling = TreeNode(label="Child 2", type="item")
tree.add_node(sibling, parent_id="root", insert_index=0) # Insert at start
Visual Structure
TreeView
├── Root Node 1
│ ├── [>] Child 1-1 # Collapsed node with children
│ ├── [ ] Child 1-2 # Leaf node (no children)
│ └── [v] Child 1-3 # Expanded node
│ ├── [ ] Grandchild
│ └── [ ] Grandchild
└── Root Node 2
└── [>] Child 2-1
Legend:
[>]- Collapsed node (has children)[v]- Expanded node (has children)[ ]- Leaf node (no children)
Expanding Nodes
Control node expansion programmatically:
# Expand all nodes with children
tree.expand_all()
# Expand specific nodes by adding to opened list
tree._state.opened.append("node-id")
Note: Users can also toggle nodes by clicking the chevron icon.
Interactive Features
Node Selection
Users can select nodes by clicking on labels. The selected node is visually highlighted:
# Programmatically select a node
tree._state.selected = "node-id"
# Check current selection
current = tree._state.selected
Adding Nodes
Users can add nodes via action buttons (visible on hover):
Add Child:
- Adds a new node as a child of the target node
- Automatically expands the parent
- Creates node with same type as parent
Add Sibling:
- Adds a new node next to the target node (same parent)
- Inserts after the target node
- Cannot add sibling to root nodes
# Programmatically add child
tree._add_child(parent_id="root", new_label="New Child")
# Programmatically add sibling
tree._add_sibling(node_id="child1", new_label="New Sibling")
Renaming Nodes
Users can rename nodes via the edit button:
- Click the edit icon (visible on hover)
- Input field appears with current label
- Press Enter to save (triggers command)
- Press ESC to cancel (keyboard shortcut)
# Programmatically start rename
tree._start_rename("node-id")
# Save rename
tree._save_rename("node-id", "New Label")
# Cancel rename
tree._cancel_rename()
Deleting Nodes
Users can delete nodes via the delete button:
Restrictions:
- Can only delete leaf nodes (no children)
- Attempting to delete a node with children raises an error
- Deleted node is removed from parent's children list
# Programmatically delete node
tree._delete_node("node-id") # Raises ValueError if node has children
Content System
Node Types and Icons
Assign types to nodes for semantic grouping and custom icon display:
# Define node types
root = TreeNode(label="Project", type="project")
folder = TreeNode(label="src", type="folder")
file = TreeNode(label="main.py", type="python-file")
# Configure icons for types
tree.set_icon_config({
"project": "fluent.folder_open",
"folder": "fluent.folder",
"python-file": "fluent.document_python"
})
Note: Icon configuration is stored in state and persists within the session.
Hierarchical Organization
Nodes automatically maintain parent-child relationships:
# Get node's children
node = tree._state.items["node-id"]
child_ids = node.children
# Get node's parent
parent_id = node.parent
# Navigate tree programmatically
for child_id in node.children:
child_node = tree._state.items[child_id]
print(child_node.label)
Finding Root Nodes
Root nodes are nodes without a parent:
root_nodes = [
node_id for node_id, node in tree._state.items.items()
if node.parent is None
]
Advanced Features
Keyboard Shortcuts
TreeView includes keyboard support for common operations:
| Key | Action |
|---|---|
ESC |
Cancel rename operation |
Additional shortcuts can be added via the Keyboard component:
from myfasthtml.controls.Keyboard import Keyboard
tree = TreeView(parent=root_instance)
# ESC handler is automatically included for cancel rename
State Management
TreeView maintains persistent state within the session:
| State Property | Type | Description |
|---|---|---|
items |
dict[str, TreeNode] |
All nodes indexed by ID |
opened |
list[str] |
IDs of expanded nodes |
selected |
str | None |
Currently selected node ID |
editing |
str | None |
Node being renamed (if any) |
icon_config |
dict[str, str] |
Type-to-icon mapping |
Dynamic Updates
TreeView updates are handled via commands that return the updated tree:
# Commands automatically target the tree for HTMX swap
cmd = tree.commands.toggle_node("node-id")
# When executed, returns updated TreeView with new state
CSS Customization
TreeView uses CSS classes for styling:
| Class | Element |
|---|---|
mf-treeview |
Root container |
mf-treenode-container |
Container for node and its children |
mf-treenode |
Individual node row |
mf-treenode.selected |
Selected node highlight |
mf-treenode-label |
Node label text |
mf-treenode-input |
Input field during rename |
mf-treenode-actions |
Action buttons container (hover) |
You can override these classes to customize appearance:
.mf-treenode.selected {
background-color: #e0f2fe;
border-left: 3px solid #0284c7;
}
.mf-treenode-actions {
opacity: 0;
transition: opacity 0.2s;
}
.mf-treenode:hover .mf-treenode-actions {
opacity: 1;
}
Examples
Example 1: File System Browser
A file/folder browser with different node types:
from fasthtml.common import *
from myfasthtml.controls.TreeView import TreeView, TreeNode
# Create tree
tree = TreeView(parent=root_instance, _id="file-browser")
# Configure icons
tree.set_icon_config({
"folder": "fluent.folder",
"python": "fluent.document_python",
"text": "fluent.document_text"
})
# Build file structure
root = TreeNode(id="root", label="my-project", type="folder")
tree.add_node(root)
src = TreeNode(id="src", label="src", type="folder")
tree.add_node(src, parent_id="root")
main = TreeNode(label="main.py", type="python")
utils = TreeNode(label="utils.py", type="python")
tree.add_node(main, parent_id="src")
tree.add_node(utils, parent_id="src")
readme = TreeNode(label="README.md", type="text")
tree.add_node(readme, parent_id="root")
# Expand to show structure
tree.expand_all()
return tree
Example 2: Category Management
Managing product categories with inline editing:
from fasthtml.common import *
from myfasthtml.controls.TreeView import TreeView, TreeNode
tree = TreeView(parent=root_instance, _id="categories")
# Root categories
electronics = TreeNode(id="elec", label="Electronics", type="category")
tree.add_node(electronics)
# Subcategories
computers = TreeNode(label="Computers", type="subcategory")
phones = TreeNode(label="Phones", type="subcategory")
tree.add_node(computers, parent_id="elec")
tree.add_node(phones, parent_id="elec")
# Products (leaf nodes)
laptop = TreeNode(label="Laptops", type="product")
desktop = TreeNode(label="Desktops", type="product")
tree.add_node(laptop, parent_id=computers.id)
tree.add_node(desktop, parent_id=computers.id)
tree.expand_all()
return tree
Example 3: Document Outline Editor
Building a document outline with headings:
from fasthtml.common import *
from myfasthtml.controls.TreeView import TreeView, TreeNode
tree = TreeView(parent=root_instance, _id="outline")
# Document structure
doc = TreeNode(id="doc", label="My Document", type="document")
tree.add_node(doc)
# Chapters
ch1 = TreeNode(id="ch1", label="Chapter 1: Introduction", type="heading1")
ch2 = TreeNode(id="ch2", label="Chapter 2: Methods", type="heading1")
tree.add_node(ch1, parent_id="doc")
tree.add_node(ch2, parent_id="doc")
# Sections
sec1_1 = TreeNode(label="1.1 Background", type="heading2")
sec1_2 = TreeNode(label="1.2 Objectives", type="heading2")
tree.add_node(sec1_1, parent_id="ch1")
tree.add_node(sec1_2, parent_id="ch1")
# Subsections
subsec = TreeNode(label="1.1.1 Historical Context", type="heading3")
tree.add_node(subsec, parent_id=sec1_1.id)
tree.expand_all()
return tree
Example 4: Dynamic Tree with Event Handling
Responding to tree events with custom logic:
from fasthtml.common import *
from myfasthtml.controls.TreeView import TreeView, TreeNode
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
tree = TreeView(parent=root_instance, _id="dynamic-tree")
# Initial structure
root = TreeNode(id="root", label="Tasks", type="folder")
tree.add_node(root)
# Function to handle selection
def on_node_selected(node_id):
# Custom logic when node is selected
node = tree._state.items[node_id]
tree._select_node(node_id)
# Update a detail panel elsewhere in the UI
return Div(
H3(f"Selected: {node.label}"),
P(f"Type: {node.type}"),
P(f"Children: {len(node.children)}")
)
# Override select command with custom handler
# (In practice, you'd extend the Commands class or use event callbacks)
tree.expand_all()
return tree
Developer Reference
This section contains technical details for developers working on the TreeView component itself.
State
The TreeView component maintains the following state properties:
| Name | Type | Description | Default |
|---|---|---|---|
items |
dict[str, TreeNode] |
All nodes indexed by ID | {} |
opened |
list[str] |
Expanded node IDs | [] |
selected |
str | None |
Selected node ID | None |
editing |
str | None |
Node being renamed | None |
icon_config |
dict[str, str] |
Type-to-icon mapping | {} |
Commands
Available commands for programmatic control:
| Name | Description |
|---|---|
toggle_node(node_id) |
Toggle expand/collapse state |
add_child(parent_id) |
Add child node to parent |
add_sibling(node_id) |
Add sibling node after target |
start_rename(node_id) |
Enter rename mode for node |
save_rename(node_id) |
Save renamed node label |
cancel_rename() |
Cancel rename operation |
delete_node(node_id) |
Delete node (if no children) |
select_node(node_id) |
Select a node |
All commands automatically target the TreeView component for HTMX updates.
Public Methods
| Method | Description |
|---|---|
add_node(node, parent_id, insert_index) |
Add a node to the tree |
expand_all() |
Expand all nodes with children |
set_icon_config(config) |
Configure icons for node types |
render() |
Render the complete TreeView |
TreeNode Dataclass
@dataclass
class TreeNode:
id: str # Unique identifier (auto-generated UUID)
label: str = "" # Display text
type: str = "default" # Node type for icon mapping
parent: Optional[str] = None # Parent node ID
children: list[str] = [] # Child node IDs
High Level Hierarchical Structure
Div(id="treeview", cls="mf-treeview")
├── Div(cls="mf-treenode-container", data-node-id="root1")
│ ├── Div(cls="mf-treenode")
│ │ ├── Icon # Toggle chevron
│ │ ├── Span(cls="mf-treenode-label") | Input(cls="mf-treenode-input")
│ │ └── Div(cls="mf-treenode-actions")
│ │ ├── Icon # Add child
│ │ ├── Icon # Rename
│ │ └── Icon # Delete
│ └── Div(cls="mf-treenode-container") # Child nodes (if expanded)
│ └── ...
├── Div(cls="mf-treenode-container", data-node-id="root2")
│ └── ...
└── Keyboard # ESC handler
Element IDs and Attributes
| Attribute | Element | Description |
|---|---|---|
id |
Root Div | TreeView component ID |
data-node-id |
Node container | Node's unique ID |
Internal Methods
These methods are used internally for rendering and state management:
| Method | Description |
|---|---|
_toggle_node(node_id) |
Toggle expand/collapse state |
_add_child(parent_id, new_label) |
Add child node implementation |
_add_sibling(node_id, new_label) |
Add sibling node implementation |
_start_rename(node_id) |
Enter rename mode |
_save_rename(node_id, node_label) |
Save renamed node |
_cancel_rename() |
Cancel rename operation |
_delete_node(node_id) |
Delete node if no children |
_select_node(node_id) |
Select a node |
_render_action_buttons(node_id) |
Render hover action buttons |
_render_node(node_id, level) |
Recursively render node and children |
Commands Class
The Commands nested class provides command factory methods:
| Method | Returns |
|---|---|
toggle_node(node_id) |
Command to toggle node |
add_child(parent_id) |
Command to add child |
add_sibling(node_id) |
Command to add sibling |
start_rename(node_id) |
Command to start rename |
save_rename(node_id) |
Command to save rename |
cancel_rename() |
Command to cancel rename |
delete_node(node_id) |
Command to delete node |
select_node(node_id) |
Command to select node |
All commands are automatically configured with HTMX targeting.
Integration with Keyboard Component
TreeView includes a Keyboard component for ESC key handling:
Keyboard(self, {"esc": self.commands.cancel_rename()}, _id="-keyboard")
This enables users to press ESC to cancel rename operations without clicking.