# 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: ```python 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: ```python tree = TreeView(parent=root_instance, _id="my-tree") ``` ### TreeNode Structure Nodes are represented by the `TreeNode` dataclass: ```python 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: ```python # 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: ```python # 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: ```python # 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 ```python # 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: 1. Click the edit icon (visible on hover) 2. Input field appears with current label 3. Press Enter to save (triggers command) 4. Press ESC to cancel (keyboard shortcut) ```python # 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 ```python # 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: ```python # 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: ```python # 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: ```python 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: ```python 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: ```python # 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: ```css .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: ```python 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: ```python 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: ```python 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: ```python 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 ```python @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: ```python Keyboard(self, {"esc": self.commands.cancel_rename()}, _id="-keyboard") ``` This enables users to press ESC to cancel rename operations without clicking.