diff --git a/src/myfasthtml/controls/Boundaries.py b/src/myfasthtml/controls/Boundaries.py
index 2568f17..9c99353 100644
--- a/src/myfasthtml/controls/Boundaries.py
+++ b/src/myfasthtml/controls/Boundaries.py
@@ -17,6 +17,7 @@ class Commands(BaseCommands):
def update_boundaries(self):
return Command(f"{self._prefix}UpdateBoundaries",
"Update component boundaries",
+ self._owner,
self._owner.update_boundaries).htmx(target=f"{self._owner.get_id()}")
diff --git a/src/myfasthtml/controls/DataGridsManager.py b/src/myfasthtml/controls/DataGridsManager.py
index d371d57..aada9f5 100644
--- a/src/myfasthtml/controls/DataGridsManager.py
+++ b/src/myfasthtml/controls/DataGridsManager.py
@@ -36,16 +36,19 @@ class Commands(BaseCommands):
def upload_from_source(self):
return Command("UploadFromSource",
"Upload from source",
+ self._owner,
self._owner.upload_from_source).htmx(target=None)
def new_grid(self):
return Command("NewGrid",
"New grid",
+ self._owner,
self._owner.new_grid)
def open_from_excel(self, tab_id, file_upload):
return Command("OpenFromExcel",
"Open from Excel",
+ self._owner,
self._owner.open_from_excel,
tab_id,
file_upload).htmx(target=f"#{self._owner._tree.get_id()}")
@@ -53,6 +56,7 @@ class Commands(BaseCommands):
def clear_tree(self):
return Command("ClearTree",
"Clear tree",
+ self._owner,
self._owner.clear_tree).htmx(target=f"#{self._owner._tree.get_id()}")
diff --git a/src/myfasthtml/controls/Dropdown.py b/src/myfasthtml/controls/Dropdown.py
index 145732f..ee303fc 100644
--- a/src/myfasthtml/controls/Dropdown.py
+++ b/src/myfasthtml/controls/Dropdown.py
@@ -10,10 +10,16 @@ from myfasthtml.core.instances import MultipleInstance
class Commands(BaseCommands):
def close(self):
- return Command("Close", "Close Dropdown", self._owner.close).htmx(target=f"#{self._owner.get_id()}-content")
+ return Command("Close",
+ "Close Dropdown",
+ self._owner,
+ self._owner.close).htmx(target=f"#{self._owner.get_id()}-content")
def click(self):
- return Command("Click", "Click on Dropdown", self._owner.on_click).htmx(target=f"#{self._owner.get_id()}-content")
+ return Command("Click",
+ "Click on Dropdown",
+ self._owner,
+ self._owner.on_click).htmx(target=f"#{self._owner.get_id()}-content")
class DropdownState:
diff --git a/src/myfasthtml/controls/FileUpload.py b/src/myfasthtml/controls/FileUpload.py
index b9f38f0..716df99 100644
--- a/src/myfasthtml/controls/FileUpload.py
+++ b/src/myfasthtml/controls/FileUpload.py
@@ -34,10 +34,16 @@ class Commands(BaseCommands):
super().__init__(owner)
def on_file_uploaded(self):
- return Command("UploadFile", "Upload file", self._owner.upload_file).htmx(target=f"#sn_{self._id}")
+ return Command("UploadFile",
+ "Upload file",
+ self._owner,
+ self._owner.upload_file).htmx(target=f"#sn_{self._id}")
def on_sheet_selected(self):
- return Command("SheetSelected", "Sheet selected", self._owner.select_sheet).htmx(target=f"#sn_{self._id}")
+ return Command("SheetSelected",
+ "Sheet selected",
+ self._owner,
+ self._owner.select_sheet).htmx(target=f"#sn_{self._id}")
class FileUpload(MultipleInstance):
diff --git a/src/myfasthtml/controls/InstancesDebugger.py b/src/myfasthtml/controls/InstancesDebugger.py
index 55d2cb1..da2e531 100644
--- a/src/myfasthtml/controls/InstancesDebugger.py
+++ b/src/myfasthtml/controls/InstancesDebugger.py
@@ -12,6 +12,7 @@ class InstancesDebugger(SingleInstance):
self._panel = Panel(self, _id="-panel")
self._command = Command("ShowInstance",
"Display selected Instance",
+ self,
self.on_network_event).htmx(target=f"#{self._panel.get_id()}_r")
def render(self):
diff --git a/src/myfasthtml/controls/Layout.py b/src/myfasthtml/controls/Layout.py
index 758a335..0be079f 100644
--- a/src/myfasthtml/controls/Layout.py
+++ b/src/myfasthtml/controls/Layout.py
@@ -37,7 +37,10 @@ class LayoutState(DbObject):
class Commands(BaseCommands):
def toggle_drawer(self, side: Literal["left", "right"]):
- return Command("ToggleDrawer", f"Toggle {side} layout drawer", self._owner.toggle_drawer, side)
+ return Command("ToggleDrawer",
+ f"Toggle {side} layout drawer",
+ self._owner,
+ self._owner.toggle_drawer, side)
def update_drawer_width(self, side: Literal["left", "right"], width: int = None):
"""
@@ -50,12 +53,11 @@ class Commands(BaseCommands):
Returns:
Command: Command object for updating drawer width
"""
- return Command(
- f"UpdateDrawerWidth_{side}",
- f"Update {side} drawer width",
- self._owner.update_drawer_width,
- side
- )
+ return Command(f"UpdateDrawerWidth_{side}",
+ f"Update {side} drawer width",
+ self._owner,
+ self._owner.update_drawer_width,
+ side)
class Layout(SingleInstance):
diff --git a/src/myfasthtml/controls/Panel.py b/src/myfasthtml/controls/Panel.py
index eecf312..36fedcc 100644
--- a/src/myfasthtml/controls/Panel.py
+++ b/src/myfasthtml/controls/Panel.py
@@ -17,7 +17,11 @@ class PanelConf:
class Commands(BaseCommands):
def toggle_side(self, side: Literal["left", "right"]):
- return Command("TogglePanelSide", f"Toggle {side} side panel", self._owner.toggle_side, side)
+ return Command("TogglePanelSide",
+ f"Toggle {side} side panel",
+ self._owner,
+ self._owner.toggle_side,
+ side)
def update_side_width(self, side: Literal["left", "right"]):
"""
@@ -29,12 +33,11 @@ class Commands(BaseCommands):
Returns:
Command: Command object for updating panel's side width
"""
- return Command(
- f"UpdatePanelSideWidth_{side}",
- f"Update {side} side panel width",
- self._owner.update_side_width,
- side
- )
+ return Command(f"UpdatePanelSideWidth_{side}",
+ f"Update {side} side panel width",
+ self._owner,
+ self._owner.update_side_width,
+ side)
class Panel(MultipleInstance):
diff --git a/src/myfasthtml/controls/Search.py b/src/myfasthtml/controls/Search.py
index b04fe48..edf7f6b 100644
--- a/src/myfasthtml/controls/Search.py
+++ b/src/myfasthtml/controls/Search.py
@@ -14,10 +14,12 @@ logger = logging.getLogger("Search")
class Commands(BaseCommands):
def search(self):
- return (Command("Search", f"Search {self._owner.items_names}", self._owner.on_search).
- htmx(target=f"#{self._owner.get_id()}-results",
- trigger="keyup changed delay:300ms",
- swap="innerHTML"))
+ return (Command("Search",
+ f"Search {self._owner.items_names}",
+ self._owner,
+ self._owner.on_search).htmx(target=f"#{self._owner.get_id()}-results",
+ trigger="keyup changed delay:300ms",
+ swap="innerHTML"))
class Search(MultipleInstance):
diff --git a/src/myfasthtml/controls/TabsManager.py b/src/myfasthtml/controls/TabsManager.py
index 05fd09f..f7825d8 100644
--- a/src/myfasthtml/controls/TabsManager.py
+++ b/src/myfasthtml/controls/TabsManager.py
@@ -60,6 +60,7 @@ class Commands(BaseCommands):
def show_tab(self, tab_id):
return Command(f"{self._prefix}ShowTab",
"Activate or show a specific tab",
+ self._owner,
self._owner.show_tab,
tab_id,
True,
@@ -68,12 +69,14 @@ class Commands(BaseCommands):
def close_tab(self, tab_id):
return Command(f"{self._prefix}CloseTab",
"Close a specific tab",
+ self._owner,
self._owner.close_tab,
tab_id).htmx(target=f"#{self._id}-controller", swap="outerHTML")
def add_tab(self, label: str, component: Any, auto_increment=False):
return Command(f"{self._prefix}AddTab",
"Add a new tab",
+ self._owner,
self._owner.on_new_tab,
label,
component,
diff --git a/src/myfasthtml/controls/TreeView.py b/src/myfasthtml/controls/TreeView.py
index a9bb56d..fd93536 100644
--- a/src/myfasthtml/controls/TreeView.py
+++ b/src/myfasthtml/controls/TreeView.py
@@ -66,74 +66,67 @@ class Commands(BaseCommands):
def toggle_node(self, node_id: str):
"""Create command to expand/collapse a node."""
- return Command(
- "ToggleNode",
- f"Toggle node {node_id}",
- self._owner._toggle_node,
- node_id
- ).htmx(target=f"#{self._owner.get_id()}")
+ return Command("ToggleNode",
+ f"Toggle node {node_id}",
+ self._owner,
+ self._owner._toggle_node,
+ node_id).htmx(target=f"#{self._owner.get_id()}")
def add_child(self, parent_id: str):
"""Create command to add a child node."""
- return Command(
- "AddChild",
- f"Add child to {parent_id}",
- self._owner._add_child,
- parent_id
- ).htmx(target=f"#{self._owner.get_id()}")
+ return Command("AddChild",
+ f"Add child to {parent_id}",
+ self._owner,
+ self._owner._add_child,
+ parent_id).htmx(target=f"#{self._owner.get_id()}")
def add_sibling(self, node_id: str):
"""Create command to add a sibling node."""
- return Command(
- "AddSibling",
- f"Add sibling to {node_id}",
- self._owner._add_sibling,
- node_id
- ).htmx(target=f"#{self._owner.get_id()}")
+ return Command("AddSibling",
+ f"Add sibling to {node_id}",
+ self._owner,
+ self._owner._add_sibling,
+ node_id
+ ).htmx(target=f"#{self._owner.get_id()}")
def start_rename(self, node_id: str):
"""Create command to start renaming a node."""
- return Command(
- "StartRename",
- f"Start renaming {node_id}",
- self._owner._start_rename,
- node_id
- ).htmx(target=f"#{self._owner.get_id()}")
+ return Command("StartRename",
+ f"Start renaming {node_id}",
+ self._owner,
+ self._owner._start_rename,
+ node_id).htmx(target=f"#{self._owner.get_id()}")
def save_rename(self, node_id: str):
"""Create command to save renamed node."""
- return Command(
- "SaveRename",
- f"Save rename for {node_id}",
- self._owner._save_rename,
- node_id
- ).htmx(target=f"#{self._owner.get_id()}")
+ return Command("SaveRename",
+ f"Save rename for {node_id}",
+ self._owner,
+ self._owner._save_rename,
+ node_id).htmx(target=f"#{self._owner.get_id()}")
def cancel_rename(self):
"""Create command to cancel renaming."""
- return Command(
- "CancelRename",
- "Cancel rename",
- self._owner._cancel_rename
- ).htmx(target=f"#{self._owner.get_id()}")
+ return Command("CancelRename",
+ "Cancel rename",
+ self._owner,
+ self._owner._cancel_rename).htmx(target=f"#{self._owner.get_id()}")
def delete_node(self, node_id: str):
"""Create command to delete a node."""
- return Command(
- "DeleteNode",
- f"Delete node {node_id}",
- self._owner._delete_node,
- node_id
- ).htmx(target=f"#{self._owner.get_id()}")
+ return Command("DeleteNode",
+ f"Delete node {node_id}",
+ self._owner,
+ self._owner._delete_node,
+ node_id).htmx(target=f"#{self._owner.get_id()}")
def select_node(self, node_id: str):
"""Create command to select a node."""
- return Command(
- "SelectNode",
- f"Select node {node_id}",
- self._owner._select_node,
- node_id
- ).htmx(target=f"#{self._owner.get_id()}")
+ return Command("SelectNode",
+ f"Select node {node_id}",
+ self._owner,
+ self._owner._select_node,
+ node_id).htmx(target=f"#{self._owner.get_id()}")
class TreeView(MultipleInstance):
@@ -187,7 +180,7 @@ class TreeView(MultipleInstance):
self._state.items[node.id] = node
if parent_id is None and node.parent is not None:
parent_id = node.parent
-
+
node.parent = parent_id
if parent_id and parent_id in self._state.items:
diff --git a/src/myfasthtml/controls/UserProfile.py b/src/myfasthtml/controls/UserProfile.py
index 7fb5372..c5fc366 100644
--- a/src/myfasthtml/controls/UserProfile.py
+++ b/src/myfasthtml/controls/UserProfile.py
@@ -33,7 +33,10 @@ class UserProfileState:
class Commands(BaseCommands):
def update_dark_mode(self):
- return Command("UpdateDarkMode", "Set the dark mode", self._owner.update_dark_mode).htmx(target=None)
+ return Command("UpdateDarkMode",
+ "Set the dark mode",
+ self._owner,
+ self._owner.update_dark_mode).htmx(target=None)
class UserProfile(SingleInstance):
diff --git a/src/myfasthtml/core/commands.py b/src/myfasthtml/core/commands.py
index 315811b..5a1e1b0 100644
--- a/src/myfasthtml/core/commands.py
+++ b/src/myfasthtml/core/commands.py
@@ -25,10 +25,11 @@ class BaseCommand:
:type description: str
"""
- def __init__(self, name, description, auto_register=True):
+ def __init__(self, name, description, owner=None, auto_register=True):
self.id = uuid.uuid4()
self.name = name
self.description = description
+ self.owner = owner
self._htmx_extra = {}
self._bindings = []
self._ft = None
@@ -133,8 +134,8 @@ class Command(BaseCommand):
:type kwargs: dict
"""
- def __init__(self, name, description, callback, *args, **kwargs):
- super().__init__(name, description)
+ def __init__(self, name, description, owner, callback, *args, **kwargs):
+ super().__init__(name, description, owner=owner)
self.callback = callback
self.callback_parameters = dict(inspect.signature(callback).parameters) if callback else {}
self.args = args
@@ -202,8 +203,8 @@ class Command(BaseCommand):
class LambdaCommand(Command):
- def __init__(self, delegate, name="LambdaCommand", description="Lambda Command"):
- super().__init__(name, description, delegate)
+ def __init__(self, owner, delegate, name="LambdaCommand", description="Lambda Command"):
+ super().__init__(name, description, owner, delegate)
self.htmx(target=None)
def execute(self, client_response: dict = None):
diff --git a/src/myfasthtml/examples/binding_datalist.py b/src/myfasthtml/examples/binding_datalist.py
index 70289f5..2517961 100644
--- a/src/myfasthtml/examples/binding_datalist.py
+++ b/src/myfasthtml/examples/binding_datalist.py
@@ -49,8 +49,14 @@ def get():
mk.manage_binding(datalist, Binding(data))
mk.manage_binding(label_elt, Binding(data))
- add_button = mk.button("Add", command=Command("Add", "Add a suggestion", add_suggestion).bind(data))
- remove_button = mk.button("Remove", command=Command("Remove", "Remove a suggestion", remove_suggestion).bind(data))
+ add_button = mk.button("Add", command=Command("Add",
+ "Add a suggestion",
+ None,
+ add_suggestion).bind(data))
+ remove_button = mk.button("Remove", command=Command("Remove",
+ "Remove a suggestion",
+ None,
+ remove_suggestion).bind(data))
return Div(
add_button,
diff --git a/src/myfasthtml/examples/clickme.py b/src/myfasthtml/examples/clickme.py
index 5c4ca88..6ade14a 100644
--- a/src/myfasthtml/examples/clickme.py
+++ b/src/myfasthtml/examples/clickme.py
@@ -11,7 +11,10 @@ def say_hello():
# Create the command
-hello_command = Command("say_hello", "Responds with a greeting", say_hello)
+hello_command = Command("say_hello",
+ "Responds with a greeting",
+ None,
+ say_hello)
# Create the app
app, rt = create_app(protect_routes=False)
diff --git a/src/myfasthtml/examples/command_with_htmx_params.py b/src/myfasthtml/examples/command_with_htmx_params.py
index 3ab2747..f01dfc0 100644
--- a/src/myfasthtml/examples/command_with_htmx_params.py
+++ b/src/myfasthtml/examples/command_with_htmx_params.py
@@ -13,7 +13,10 @@ def change_text():
return "New text"
-command = Command("change_text", "change the text", change_text).htmx(target="#text")
+command = Command("change_text",
+ "change the text",
+ None,
+ change_text).htmx(target="#text")
@rt("/")
diff --git a/tests/controls/test_helpers.py b/tests/controls/test_helpers.py
index 5269e13..3f66293 100644
--- a/tests/controls/test_helpers.py
+++ b/tests/controls/test_helpers.py
@@ -45,7 +45,7 @@ def test_i_can_mk_button_with_attrs():
def test_i_can_mk_button_with_command(user, rt):
def new_value(value): return value
- command = Command('test', 'TestingCommand', new_value, "this is my new value")
+ command = Command('test', 'TestingCommand', None, new_value, "this is my new value")
@rt('/')
def get(): return mk.button('button', command)
diff --git a/tests/core/test_commands.py b/tests/core/test_commands.py
index 5105623..842c718 100644
--- a/tests/core/test_commands.py
+++ b/tests/core/test_commands.py
@@ -27,21 +27,21 @@ def reset_command_manager():
class TestCommandDefault:
def test_i_can_create_a_command_with_no_params(self):
- command = Command('test', 'Command description', callback)
+ command = Command('test', 'Command description', None, callback)
assert command.id is not None
assert command.name == 'test'
assert command.description == 'Command description'
assert command.execute() == "Hello World"
def test_command_are_registered(self):
- command = Command('test', 'Command description', callback)
+ command = Command('test', 'Command description', None, callback)
assert CommandsManager.commands.get(str(command.id)) is command
class TestCommandBind:
def test_i_can_bind_a_command_to_an_element(self):
- command = Command('test', 'Command description', callback)
+ command = Command('test', 'Command description', None, callback)
elt = Button()
updated = command.bind_ft(elt)
@@ -50,7 +50,7 @@ class TestCommandBind:
assert matches(updated, expected)
def test_i_can_suppress_swapping_with_target_attr(self):
- command = Command('test', 'Command description', callback).htmx(target=None)
+ command = Command('test', 'Command description', None, callback).htmx(target=None)
elt = Button()
updated = command.bind_ft(elt)
@@ -70,7 +70,7 @@ class TestCommandBind:
make_observable(data)
bind(data, "value", on_data_change)
- command = Command('test', 'Command description', another_callback).bind(data)
+ command = Command('test', 'Command description', None, another_callback).bind(data)
res = command.execute()
@@ -88,14 +88,14 @@ class TestCommandBind:
make_observable(data)
bind(data, "value", on_data_change)
- command = Command('test', 'Command description', another_callback).bind(data)
+ command = Command('test', 'Command description', None, another_callback).bind(data)
res = command.execute()
assert res == ["another 1", "another 2", ("hello", "new value")]
def test_by_default_swap_is_set_to_outer_html(self):
- command = Command('test', 'Command description', callback)
+ command = Command('test', 'Command description', None, callback)
elt = Button()
updated = command.bind_ft(elt)
@@ -113,7 +113,7 @@ class TestCommandBind:
def another_callback():
return return_values
- command = Command('test', 'Command description', another_callback)
+ command = Command('test', 'Command description', None, another_callback)
res = command.execute()
@@ -125,7 +125,7 @@ class TestCommandBind:
class TestCommandExecute:
def test_i_can_create_a_command_with_no_params(self):
- command = Command('test', 'Command description', callback)
+ command = Command('test', 'Command description', None, callback)
assert command.id is not None
assert command.name == 'test'
assert command.description == 'Command description'
@@ -137,7 +137,7 @@ class TestCommandExecute:
def callback_with_param(param):
return f"Hello {param}"
- command = Command('test', 'Command description', callback_with_param, "world")
+ command = Command('test', 'Command description', None, callback_with_param, "world")
assert command.execute() == "Hello world"
def test_i_can_execute_a_command_with_open_parameter(self):
@@ -146,7 +146,7 @@ class TestCommandExecute:
def callback_with_param(name):
return f"Hello {name}"
- command = Command('test', 'Command description', callback_with_param)
+ command = Command('test', 'Command description', None, callback_with_param)
assert command.execute(client_response={"name": "world"}) == "Hello world"
def test_i_can_convert_arg_in_execute(self):
@@ -155,7 +155,7 @@ class TestCommandExecute:
def callback_with_param(number: int):
assert isinstance(number, int)
- command = Command('test', 'Command description', callback_with_param)
+ command = Command('test', 'Command description', None, callback_with_param)
command.execute(client_response={"number": "10"})
def test_swap_oob_is_added_when_multiple_elements_are_returned(self):
@@ -164,7 +164,7 @@ class TestCommandExecute:
def another_callback():
return Div(id="first"), Div(id="second"), "hello", Div(id="third")
- command = Command('test', 'Command description', another_callback)
+ command = Command('test', 'Command description', None, another_callback)
res = command.execute()
assert "hx-swap-oob" not in res[0].attrs
@@ -177,7 +177,7 @@ class TestCommandExecute:
def another_callback():
return Div(id="first"), Div(), "hello", Div()
- command = Command('test', 'Command description', another_callback)
+ command = Command('test', 'Command description', None, another_callback)
res = command.execute()
assert "hx-swap-oob" not in res[0].attrs
@@ -188,9 +188,9 @@ class TestCommandExecute:
class TestLambaCommand:
def test_i_can_create_a_command_from_lambda(self):
- command = LambdaCommand(lambda resp: "Hello World")
+ command = LambdaCommand(None, lambda resp: "Hello World")
assert command.execute() == "Hello World"
def test_by_default_target_is_none(self):
- command = LambdaCommand(lambda resp: "Hello World")
+ command = LambdaCommand(None, lambda resp: "Hello World")
assert command.get_htmx_params()["hx-swap"] == "none"
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 60f3183..f8e7900 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -34,13 +34,13 @@ def rt(user):
class TestingCommand:
def test_i_can_trigger_a_command(self, user):
- command = Command('test', 'TestingCommand', new_value, "this is my new value")
+ command = Command('test', 'TestingCommand', None, new_value, "this is my new value")
testable = TestableElement(user, mk.button('button', command))
testable.click()
assert user.get_content() == "this is my new value"
def test_error_is_raised_when_command_is_not_found(self, user):
- command = Command('test', 'TestingCommand', new_value, "this is my new value")
+ command = Command('test', 'TestingCommand', None, new_value, "this is my new value")
CommandsManager.reset()
testable = TestableElement(user, mk.button('button', command))
@@ -50,7 +50,7 @@ class TestingCommand:
assert "not found." in str(exc_info.value)
def test_i_can_play_a_complex_scenario(self, user, rt):
- command = Command('test', 'TestingCommand', new_value, "this is my new value")
+ command = Command('test', 'TestingCommand', None, new_value, "this is my new value")
@rt('/')
def get(): return mk.button('button', command)
diff --git a/tests/testclient/test_matches.py b/tests/testclient/test_matches.py
index 6fac11d..8ed7d6c 100644
--- a/tests/testclient/test_matches.py
+++ b/tests/testclient/test_matches.py
@@ -463,7 +463,7 @@ class TestPredicates:
div = Div(hx_post="/url")
assert HasHtmx(hx_post="/url").validate(div)
- c = Command("c", "testing has_htmx", None)
+ c = Command("c", "testing has_htmx", None, None)
c.bind_ft(div)
assert HasHtmx(command=c).validate(div)