# Developer Control Mode You are now in **Developer Control Mode** - specialized mode for developing UI controls in the MyFastHtml project. ## Primary Objective Create robust, consistent UI controls by following the established patterns and rules of the project. ## Control Development Rules (DEV-CONTROL) ### DEV-CONTROL-01: Class Inheritance A control must inherit from one of the three base classes based on its usage: | Class | Usage | Example | |-------|-------|---------| | `MultipleInstance` | Multiple instances possible per session | `DataGrid`, `Panel`, `Search` | | `SingleInstance` | One instance per session | `Layout`, `UserProfile`, `CommandsDebugger` | | `UniqueInstance` | One instance, but `__init__` called each time | (special case) | ```python from myfasthtml.core.instances import MultipleInstance class MyControl(MultipleInstance): def __init__(self, parent, _id=None): super().__init__(parent, _id=_id) ``` --- ### DEV-CONTROL-02: Nested Commands Class Each interactive control must define a `Commands` class inheriting from `BaseCommands`: ```python from myfasthtml.controls.BaseCommands import BaseCommands from myfasthtml.core.commands import Command class Commands(BaseCommands): def my_action(self): return Command("MyAction", "Description of the action", self._owner, self._owner.my_action_handler ).htmx(target=f"#{self._id}") ``` **Conventions**: - Method name in `snake_case` - First `Command` argument: unique name (PascalCase recommended) - Use `self._owner` to reference the parent control - Use `self._id` for HTMX targets --- ### DEV-CONTROL-03: State Management with DbObject Persistent state must be encapsulated in a class inheriting from `DbObject`: ```python from myfasthtml.core.dbmanager import DbObject class MyControlState(DbObject): def __init__(self, owner, save_state=True): with self.initializing(): super().__init__(owner, save_state=save_state) # Persisted attributes self.visible: bool = True self.width: int = 250 # NOT persisted (ns_ prefix) self.ns_temporary_data = None # NOT saved but evaluated (ne_ prefix) self.ne_computed_value = None ``` **Special prefixes**: - `ns_` (no-save): not persisted to database - `ne_` (no-equality): not compared for change detection - `_`: internal variables, ignored --- ### DEV-CONTROL-04: render() and __ft__() Methods Each control must implement: ```python def render(self): return Div( # Control content id=self._id, cls="mf-my-control" ) def __ft__(self): return self.render() ``` **Rules**: - `render()` contains the rendering logic - `__ft__()` simply delegates to `render()` - Root element must have `id=self._id` --- ### DEV-CONTROL-05: Control Initialization Standard initialization structure: ```python def __init__(self, parent, _id=None, **kwargs): super().__init__(parent, _id=_id) # 1. State self._state = MyControlState(self) # 2. Commands self.commands = Commands(self) # 3. Sub-components self._panel = Panel(self, _id="-panel") self._search = Search(self, _id="-search") # 4. Command bindings self._search.bind_command("Search", self.commands.on_search()) ``` --- ### DEV-CONTROL-06: Relative IDs for Sub-components Use the `-` prefix to create IDs relative to the parent: ```python # Results in: "{parent_id}-panel" self._panel = Panel(self, _id="-panel") # Results in: "{parent_id}-search" self._search = Search(self, _id="-search") ``` --- ### DEV-CONTROL-07: Using the mk Helper Class Use `mk` helpers to create interactive elements: ```python from myfasthtml.controls.helpers import mk # Button with command mk.button("Click me", command=self.commands.my_action()) # Icon with command and tooltip mk.icon(my_icon, command=self.commands.toggle(), tooltip="Toggle") # Label with icon mk.label("Title", icon=my_icon, size="sm") # Generic wrapper mk.mk(Input(...), command=self.commands.on_input()) ``` --- ### DEV-CONTROL-08: Logging Each control must declare a logger with its name: ```python import logging logger = logging.getLogger("MyControl") class MyControl(MultipleInstance): def my_action(self): logger.debug(f"my_action called with {param=}") ``` --- ### DEV-CONTROL-09: Command Binding Between Components To link a sub-component's actions to the parent control: ```python # In the parent control self._child = ChildControl(self, _id="-child") self._child.bind_command("ChildAction", self.commands.on_child_action()) ``` --- ### DEV-CONTROL-10: Keyboard and Mouse Composition For interactive controls, compose `Keyboard` and `Mouse`: ```python from myfasthtml.controls.Keyboard import Keyboard from myfasthtml.controls.Mouse import Mouse def render(self): return Div( self._mk_content(), Keyboard(self, _id="-keyboard").add("esc", self.commands.close()), Mouse(self, _id="-mouse").add("click", self.commands.handle_on_click()), id=self._id ) ``` --- ### DEV-CONTROL-11: Partial Rendering For HTMX updates, implement partial rendering methods: ```python def render_partial(self, fragment="default"): if fragment == "body": return self._mk_body() elif fragment == "header": return self._mk_header() return self._mk_default() ``` --- ### DEV-CONTROL-12: Simple State (Non-Persisted) For simple state without DB persistence, use a basic Python class: ```python class MyControlState: def __init__(self): self.opened = False self.selected = None ``` --- ### DEV-CONTROL-13: Dataclasses for Configurations Use dataclasses for configurations: ```python from dataclasses import dataclass from typing import Optional @dataclass class MyControlConf: title: str = "Default" show_header: bool = True width: Optional[int] = None ``` --- ### DEV-CONTROL-14: Generated ID Prefixes Use short, meaningful prefixes for sub-elements: ```python f"tb_{self._id}" # table body f"th_{self._id}" # table header f"sn_{self._id}" # sheet name f"fi_{self._id}" # file input ``` --- ### DEV-CONTROL-15: State Getters Expose state via getter methods: ```python def get_state(self): return self._state def get_selected(self): return self._state.selected ``` --- ### DEV-CONTROL-16: Computed Properties Use `@property` for frequent access: ```python @property def width(self): return self._state.width ``` --- ### DEV-CONTROL-17: JavaScript Initialization Scripts If the control requires JavaScript, include it in the render: ```python from fasthtml.xtend import Script def render(self): return Div( self._mk_content(), Script(f"initMyControl('{self._id}');"), id=self._id ) ``` --- ### DEV-CONTROL-18: CSS Classes with Prefix Use the `mf-` prefix for custom CSS classes: ```python cls="mf-my-control" cls="mf-my-control-header" ``` --- ### DEV-CONTROL-19: Sub-element Creation Methods Prefix creation methods with `_mk_` or `mk_`: ```python def _mk_header(self): """Private creation method""" return Div(...) def mk_content(self): """Public creation method (reusable)""" return Div(...) ``` --- ### DEV-CONTROL-20: HTMX Ajax Requests **Always specify a `target` in HTMX ajax requests.** ```javascript // ❌ INCORRECT: Without target, HTMX doesn't know where to swap the response htmx.ajax('POST', '/url', { values: {param: value} }); // ✅ CORRECT: Explicitly specify the target htmx.ajax('POST', '/url', { target: '#element-id', values: {param: value} }); ``` **Exception:** Response contains elements with `hx-swap-oob="true"`. --- ### DEV-CONTROL-21: HTMX Swap Modes and Event Listeners **`hx-on::after-settle` only works when the swapped element replaces the target (`outerHTML`).** ```javascript // ❌ INCORRECT: innerHTML (default) nests the returned element // The hx-on::after-settle attribute on the returned element is never processed htmx.ajax('POST', '/url', { target: '#my-element' // swap: 'innerHTML' is the default }); // ✅ CORRECT: outerHTML replaces the entire element // The hx-on::after-settle attribute on the returned element works htmx.ajax('POST', '/url', { target: '#my-element', swap: 'outerHTML' }); ``` **Why:** - `innerHTML`: replaces **content** → `