I can bind elements

This commit is contained in:
2025-11-01 23:44:18 +01:00
parent 991a6f07ff
commit aaba6a5468
14 changed files with 463 additions and 109 deletions

View File

@@ -0,0 +1,114 @@
import logging
import uuid
from typing import Optional
from fasthtml.fastapp import fast_app
from myutils.observable import make_observable, bind, collect_return_values
from myfasthtml.core.constants import Routes, ROUTE_ROOT
from myfasthtml.core.utils import get_default_attr
bindings_app, bindings_rt = fast_app()
logger = logging.getLogger("Bindings")
class Binding:
def __init__(self, data, attr=None, ft=None, ft_name=None, ft_attr=None):
"""
Creates a new binding object between a data object used as a pivot and an HTML element.
The same pivot object must be used for different bindings.
This will allow the binding between the HTML elements
:param data: object used as a pivot
:param attr: attribute of the data object
:param ft: HTML element to bind to
:param ft_name: name of the HTML element to bind to (send by the form)
:param ft_attr: value of the attribute to bind to (send by the form)
"""
self.id = uuid.uuid4()
self.htmx_extra = {}
self.data = data
self.data_attr = attr or get_default_attr(data)
self.ft = self._safe_ft(ft)
self.ft_name = ft_name
self.ft_attr = ft_attr
make_observable(self.data)
bind(self.data, self.data_attr, self.notify)
# register the command
BindingsManager.register(self)
def bind_ft(self, ft, name, attr=None):
"""
Update the elements to bind to
:param ft:
:param name:
:param attr:
:return:
"""
self.ft = self._safe_ft(ft)
self.ft_name = name
self.ft_attr = attr
def get_htmx_params(self):
return self.htmx_extra | {
"hx-post": f"{ROUTE_ROOT}{Routes.Bindings}",
"hx-vals": f'{{"b_id": "{self.id}"}}',
}
def notify(self, old, new):
logger.debug(f"Binding '{self.id}': Changing from '{old}' to '{new}'")
if self.ft_attr is None:
self.ft.children = (new,)
else:
self.ft.attrs[self.ft_attr] = new
self.ft.attrs["hx-swap-oob"] = "true"
return self.ft
def update(self, values: dict):
logger.debug(f"Binding '{self.id}': Updating with {values=}.")
for key, value in values.items():
if key == self.ft_name:
setattr(self.data, self.data_attr, value)
res = collect_return_values(self.data)
return res
else:
logger.debug(f"Nothing to trigger in {values}.")
return None
@staticmethod
def _safe_ft(ft):
"""
Make sure the ft has an id.
:param ft:
:return:
"""
if ft is None:
return None
if ft.attrs.get("id", None) is None:
ft.attrs["id"] = str(uuid.uuid4())
return ft
def htmx(self, trigger=None):
if trigger:
self.htmx_extra["hx-trigger"] = trigger
return self
class BindingsManager:
bindings = {}
@staticmethod
def register(binding: Binding):
BindingsManager.bindings[str(binding.id)] = binding
@staticmethod
def get_binding(binding_id: str) -> Optional[Binding]:
return BindingsManager.bindings.get(str(binding_id))
@staticmethod
def reset():
return BindingsManager.bindings.clear()