diff --git a/src/myfasthtml/controls/CommandsDebugger.py b/src/myfasthtml/controls/CommandsDebugger.py
index 312897a..8168ff9 100644
--- a/src/myfasthtml/controls/CommandsDebugger.py
+++ b/src/myfasthtml/controls/CommandsDebugger.py
@@ -5,6 +5,10 @@ from myfasthtml.core.network_utils import from_parent_child_list
class CommandsDebugger(SingleInstance):
+ """
+ Represents a debugger designed for visualizing and managing commands in a parent-child
+ hierarchical structure.
+ """
def __init__(self, parent, _id=None):
super().__init__(parent, _id=_id)
diff --git a/src/myfasthtml/controls/Dropdown.py b/src/myfasthtml/controls/Dropdown.py
index 20e1695..145732f 100644
--- a/src/myfasthtml/controls/Dropdown.py
+++ b/src/myfasthtml/controls/Dropdown.py
@@ -22,6 +22,12 @@ class DropdownState:
class Dropdown(MultipleInstance):
+ """
+ Represents a dropdown component that can be toggled open or closed. This class is used
+ to create interactive dropdown elements, allowing for container and button customization.
+ The dropdown provides functionality to manage its state, including opening, closing, and
+ handling user interactions.
+ """
def __init__(self, parent, content=None, button=None, _id=None):
super().__init__(parent, _id=_id)
self.button = Div(button) if not isinstance(button, FT) else button
diff --git a/src/myfasthtml/controls/FileUpload.py b/src/myfasthtml/controls/FileUpload.py
index 2c828ab..7fbf3f0 100644
--- a/src/myfasthtml/controls/FileUpload.py
+++ b/src/myfasthtml/controls/FileUpload.py
@@ -35,6 +35,14 @@ class Commands(BaseCommands):
class FileUpload(MultipleInstance):
+ """
+ Represents a file upload component.
+
+ This class provides functionality to handle the uploading process of a file,
+ extract sheet names from an Excel file, and enables users to select a specific
+ sheet for further processing. It integrates commands and state management
+ to ensure smooth operation within a parent application.
+ """
def __init__(self, parent, _id=None):
super().__init__(parent, _id=_id)
diff --git a/src/myfasthtml/controls/InstancesDebugger.py b/src/myfasthtml/controls/InstancesDebugger.py
index cda50e1..5528b64 100644
--- a/src/myfasthtml/controls/InstancesDebugger.py
+++ b/src/myfasthtml/controls/InstancesDebugger.py
@@ -1,5 +1,6 @@
from myfasthtml.controls.Panel import Panel
from myfasthtml.controls.VisNetwork import VisNetwork
+from myfasthtml.core.commands import LambdaCommand
from myfasthtml.core.instances import SingleInstance, InstancesManager
from myfasthtml.core.network_utils import from_parent_child_list
@@ -11,7 +12,8 @@ class InstancesDebugger(SingleInstance):
def render(self):
nodes, edges = self._get_nodes_and_edges()
- vis_network = VisNetwork(self, nodes=nodes, edges=edges, _id="-vis")
+ c = LambdaCommand(lambda event_data: print("received", event_data))
+ vis_network = VisNetwork(self, nodes=nodes, edges=edges, _id="-vis", events_handlers={"select_node": c})
return self._panel.set_main(vis_network)
def _get_nodes_and_edges(self):
diff --git a/src/myfasthtml/controls/Keyboard.py b/src/myfasthtml/controls/Keyboard.py
index f3c2d77..ff7e4d8 100644
--- a/src/myfasthtml/controls/Keyboard.py
+++ b/src/myfasthtml/controls/Keyboard.py
@@ -7,6 +7,12 @@ from myfasthtml.core.instances import MultipleInstance
class Keyboard(MultipleInstance):
+ """
+ Represents a keyboard with customizable key combinations support.
+
+ The Keyboard class allows managing key combinations and their corresponding
+ actions for a given parent object.
+ """
def __init__(self, parent, combinations=None, _id=None):
super().__init__(parent, _id=_id)
self.combinations = combinations or {}
diff --git a/src/myfasthtml/controls/Mouse.py b/src/myfasthtml/controls/Mouse.py
index 4149d1d..8f97124 100644
--- a/src/myfasthtml/controls/Mouse.py
+++ b/src/myfasthtml/controls/Mouse.py
@@ -7,6 +7,12 @@ from myfasthtml.core.instances import MultipleInstance
class Mouse(MultipleInstance):
+ """
+ Represents a mechanism to manage mouse event combinations and their associated commands.
+
+ This class is used to add, manage, and render mouse event sequences with corresponding
+ commands, providing a flexible way to handle mouse interactions programmatically.
+ """
def __init__(self, parent, _id=None, combinations=None):
super().__init__(parent, _id=_id)
self.combinations = combinations or {}
diff --git a/src/myfasthtml/controls/Panel.py b/src/myfasthtml/controls/Panel.py
index 66a2bbf..74f2961 100644
--- a/src/myfasthtml/controls/Panel.py
+++ b/src/myfasthtml/controls/Panel.py
@@ -38,6 +38,14 @@ class Commands(BaseCommands):
class Panel(MultipleInstance):
+ """
+ Represents a user interface panel that supports customizable left, main, and right components.
+
+ The `Panel` class is used to create and manage a panel layout with optional left, main,
+ and right sections. It provides functionality to set the components of the panel, toggle
+ sides, and adjust the width of the sides dynamically. The class also handles rendering
+ the panel with appropriate HTML elements and JavaScript for interactivity.
+ """
def __init__(self, parent, conf=None, _id=None):
super().__init__(parent, _id=_id)
self.conf = conf or PanelConf()
diff --git a/src/myfasthtml/controls/Search.py b/src/myfasthtml/controls/Search.py
index 67fbd58..a00e4b7 100644
--- a/src/myfasthtml/controls/Search.py
+++ b/src/myfasthtml/controls/Search.py
@@ -21,6 +21,21 @@ class Commands(BaseCommands):
class Search(MultipleInstance):
+ """
+ Represents a component for managing and filtering a list of items.
+ It uses fuzzy matching and subsequence matching to filter items.
+
+ :ivar items_names: The name of the items used to filter.
+ :type items_names: str
+ :ivar items: The first set of items to filter.
+ :type items: list
+ :ivar filtered: A copy of the `items` list, representing the filtered items after a search operation.
+ :type filtered: list
+ :ivar get_attr: Callable function to extract string values from items for filtering.
+ :type get_attr: Callable[[Any], str]
+ :ivar template: Callable function to define how filtered items are rendered.
+ :type template: Callable[[Any], Any]
+ """
def __init__(self,
parent: BaseInstance,
_id=None,
diff --git a/src/myfasthtml/controls/VisNetwork.py b/src/myfasthtml/controls/VisNetwork.py
index a1da6fa..67b7882 100644
--- a/src/myfasthtml/controls/VisNetwork.py
+++ b/src/myfasthtml/controls/VisNetwork.py
@@ -25,19 +25,33 @@ class VisNetworkState(DbObject):
},
"physics": {"enabled": True}
}
+ self.events_handlers: dict = {} # {event_name: command_url}
class VisNetwork(MultipleInstance):
- def __init__(self, parent, _id=None, nodes=None, edges=None, options=None):
+ def __init__(self, parent, _id=None, nodes=None, edges=None, options=None, events_handlers=None):
super().__init__(parent, _id=_id)
logger.debug(f"VisNetwork created with id: {self._id}")
+ # possible events (expected in snake_case
+ # - select_node → selectNode
+ # - select → select
+ # - click → click
+ # - double_click → doubleClick
+
self._state = VisNetworkState(self)
- self._update_state(nodes, edges, options)
+
+ # Convert Commands to URLs
+ handlers_htmx_options = {
+ event_name: command.ajax_htmx_options()
+ for event_name, command in events_handlers.items()
+ } if events_handlers else {}
+
+ self._update_state(nodes, edges, options, handlers_htmx_options)
- def _update_state(self, nodes, edges, options):
- logger.debug(f"Updating VisNetwork state with {nodes=}, {edges=}, {options=}")
- if not nodes and not edges and not options:
+ def _update_state(self, nodes, edges, options, events_handlers=None):
+ logger.debug(f"Updating VisNetwork state with {nodes=}, {edges=}, {options=}, {events_handlers=}")
+ if not nodes and not edges and not options and not events_handlers:
return
state = self._state.copy()
@@ -47,6 +61,8 @@ class VisNetwork(MultipleInstance):
state.edges = edges
if options is not None:
state.options = options
+ if events_handlers is not None:
+ state.events_handlers = events_handlers
self._state.update(state)
@@ -70,6 +86,32 @@ class VisNetwork(MultipleInstance):
# Convert Python options to JS
js_options = json.dumps(self._state.options, indent=2)
+ # Map Python event names to vis-network event names
+ event_name_map = {
+ "select_node": "selectNode",
+ "select": "select",
+ "click": "click",
+ "double_click": "doubleClick"
+ }
+
+ # Generate event handlers JavaScript
+ event_handlers_js = ""
+ for event_name, command_htmx_options in self._state.events_handlers.items():
+ vis_event_name = event_name_map.get(event_name, event_name)
+ event_handlers_js += f"""
+ network.on('{vis_event_name}', function(params) {{
+ const event_data = {{
+ nodes: params.nodes,
+ edges: params.edges,
+ pointer: params.pointer
+ }};
+ htmx.ajax('POST', '{command_htmx_options['url']}', {{
+ values: {{event_data: JSON.stringify(event_data)}},
+ swap: 'none'
+ }});
+ }});
+"""
+
return (
Div(
id=self._id,
@@ -92,6 +134,7 @@ class VisNetwork(MultipleInstance):
}};
const options = {js_options};
const network = new vis.Network(container, data, options);
+ {event_handlers_js}
}})();
""")
)
diff --git a/src/myfasthtml/core/commands.py b/src/myfasthtml/core/commands.py
index c58272b..0f56e3b 100644
--- a/src/myfasthtml/core/commands.py
+++ b/src/myfasthtml/core/commands.py
@@ -97,6 +97,16 @@ class BaseCommand:
def url(self):
return f"{ROUTE_ROOT}{Routes.Commands}?c_id={self.id}"
+ def ajax_htmx_options(self):
+ res = {"url": self.url}
+ if "hx-target" in self._htmx_extra:
+ res["target"] = self._htmx_extra["hx-target"]
+ if "hx-swap" in self._htmx_extra:
+ res["swap"] = self._htmx_extra["hx-swap"]
+ res["values"] = {}
+
+ return res
+
def get_ft(self):
return self._ft
@@ -143,6 +153,13 @@ class Command(BaseCommand):
return value.split(",")
return value
+ def ajax_htmx_options(self):
+ res = super().ajax_htmx_options()
+ if self.kwargs:
+ res["values"] |= self.kwargs
+ res["values"]["c_id"] = f"{self.id}" # cannot be overridden
+ return res
+
def execute(self, client_response: dict = None):
ret_from_bindings = []
diff --git a/tests/controls/conftest.py b/tests/controls/conftest.py
index fd1170e..0acbb37 100644
--- a/tests/controls/conftest.py
+++ b/tests/controls/conftest.py
@@ -1,6 +1,6 @@
import pytest
-from myfasthtml.core.instances import SingleInstance
+from myfasthtml.core.instances import SingleInstance, InstancesManager
class RootInstanceForTests(SingleInstance):
@@ -25,4 +25,5 @@ def session():
@pytest.fixture(scope="session")
def root_instance(session):
+ InstancesManager.reset()
return RootInstanceForTests(session=session)
diff --git a/tests/controls/test_layout.py b/tests/controls/test_layout.py
index 06aa84c..9ac9385 100644
--- a/tests/controls/test_layout.py
+++ b/tests/controls/test_layout.py
@@ -35,15 +35,6 @@ class TestLayoutBehaviour:
assert layout._main_content == content
assert result == layout # Should return self for chaining
- def test_i_can_set_footer_content(self, root_instance):
- """Test setting footer content."""
- layout = Layout(root_instance, app_name="Test App")
- content = Div("Footer content")
-
- layout.set_footer(content)
-
- assert layout._footer_content == content
-
def test_i_can_add_content_to_left_drawer(self, root_instance):
"""Test adding content to left drawer."""
layout = Layout(root_instance, app_name="Test App")