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)