I can bind radio
This commit is contained in:
@@ -7,7 +7,7 @@ from fasthtml.fastapp import fast_app
|
||||
from myutils.observable import make_observable, bind, collect_return_values, unbind
|
||||
|
||||
from myfasthtml.core.constants import Routes, ROUTE_ROOT
|
||||
from myfasthtml.core.utils import get_default_attr
|
||||
from myfasthtml.core.utils import get_default_attr, get_default_ft_attr, is_checkbox, is_radio
|
||||
|
||||
bindings_app, bindings_rt = fast_app()
|
||||
logger = logging.getLogger("Bindings")
|
||||
@@ -61,27 +61,29 @@ class AttrPresentDetection(AttrChangedDetection):
|
||||
|
||||
|
||||
class FtUpdate:
|
||||
def update(self, ft, ft_name, ft_attr, old, new):
|
||||
def update(self, ft, ft_name, ft_attr, old, new, converter):
|
||||
pass
|
||||
|
||||
|
||||
class ValueChangeFtUpdate(FtUpdate):
|
||||
def update(self, ft, ft_name, ft_attr, old, new):
|
||||
def update(self, ft, ft_name, ft_attr, old, new, converter):
|
||||
# simple mode, just update the text or the attribute
|
||||
new_to_use = converter.convert(new) if converter else new
|
||||
if ft_attr is None:
|
||||
ft.children = (new,)
|
||||
ft.children = (new_to_use,)
|
||||
else:
|
||||
ft.attrs[ft_attr] = new
|
||||
ft.attrs[ft_attr] = new_to_use
|
||||
return ft
|
||||
|
||||
|
||||
class AttributePresenceFtUpdate(FtUpdate):
|
||||
def update(self, ft, ft_name, ft_attr, old, new):
|
||||
def update(self, ft, ft_name, ft_attr, old, new, converter):
|
||||
# attribute presence mode, toggle the attribute (add or remove it)
|
||||
new_to_use = converter.convert(new) if converter else new
|
||||
if ft_attr is None:
|
||||
ft.children = (bool(new),)
|
||||
ft.children = (bool(new_to_use),)
|
||||
else:
|
||||
ft.attrs[ft_attr] = "true" if new else None # FastHtml auto remove None attributes
|
||||
ft.attrs[ft_attr] = "true" if new_to_use else None # FastHtml auto remove None attributes
|
||||
return ft
|
||||
|
||||
|
||||
@@ -104,6 +106,14 @@ class BooleanConverter(DataConverter):
|
||||
return False
|
||||
|
||||
|
||||
class RadioConverter(DataConverter):
|
||||
def __init__(self, radio_value):
|
||||
self.radio_value = radio_value
|
||||
|
||||
def convert(self, data):
|
||||
return data == self.radio_value
|
||||
|
||||
|
||||
class Binding:
|
||||
def __init__(self, data: Any, attr: str = None):
|
||||
"""
|
||||
@@ -136,8 +146,8 @@ class Binding:
|
||||
|
||||
def bind_ft(self,
|
||||
ft,
|
||||
name,
|
||||
attr=None,
|
||||
name=None,
|
||||
data_converter: DataConverter = None,
|
||||
detection_mode: DetectionMode = None,
|
||||
update_mode: UpdateMode = None):
|
||||
@@ -159,18 +169,37 @@ class Binding:
|
||||
if self._is_active:
|
||||
self.deactivate()
|
||||
|
||||
if ft.tag in ["input"]:
|
||||
# I must not force the htmx
|
||||
if {"hx-post", "hx_post"} & set(ft.attrs.keys()):
|
||||
raise ValueError(f"Binding '{self.id}': htmx post already set on input.")
|
||||
|
||||
# update the component to post on the correct route input and forms only
|
||||
htmx = self.get_htmx_params()
|
||||
ft.attrs |= htmx
|
||||
|
||||
# Configure UI elements
|
||||
self.ft = self._safe_ft(ft)
|
||||
self.ft_name = name
|
||||
self.ft_attr = attr
|
||||
self.ft_name = name or ft.attrs.get("name")
|
||||
self.ft_attr = attr or get_default_ft_attr(ft)
|
||||
|
||||
if is_checkbox(ft):
|
||||
default_data_converter = BooleanConverter()
|
||||
default_detection_mode = DetectionMode.AttributePresence
|
||||
default_update_mode = UpdateMode.AttributePresence
|
||||
elif is_radio(ft):
|
||||
default_data_converter = RadioConverter(ft.attrs["value"])
|
||||
default_detection_mode = DetectionMode.ValueChange
|
||||
default_update_mode = UpdateMode.AttributePresence
|
||||
else:
|
||||
default_data_converter = None
|
||||
default_detection_mode = DetectionMode.ValueChange
|
||||
default_update_mode = UpdateMode.ValueChange
|
||||
|
||||
# Update optional parameters if provided
|
||||
if data_converter is not None:
|
||||
self.data_converter = data_converter
|
||||
if detection_mode is not None:
|
||||
self.detection_mode = detection_mode
|
||||
if update_mode is not None:
|
||||
self.update_mode = update_mode
|
||||
self.data_converter = data_converter or default_data_converter
|
||||
self.detection_mode = detection_mode or default_detection_mode
|
||||
self.update_mode = update_mode or default_update_mode
|
||||
|
||||
# Create strategy objects
|
||||
self._detection = self._factory(self.detection_mode)
|
||||
@@ -187,6 +216,16 @@ class Binding:
|
||||
"hx-vals": f'{{"b_id": "{self.id}"}}',
|
||||
}
|
||||
|
||||
def init(self):
|
||||
"""
|
||||
Initialise the UI element with the value of the data
|
||||
:return:
|
||||
"""
|
||||
old_value = None # to complicated to retrieve as it depends on the nature of self.ft
|
||||
new_value = getattr(self.data, self.data_attr)
|
||||
self.notify(old_value, new_value)
|
||||
return self
|
||||
|
||||
def notify(self, old, new):
|
||||
"""
|
||||
Callback when the data attribute changes.
|
||||
@@ -204,16 +243,21 @@ class Binding:
|
||||
return None
|
||||
|
||||
logger.debug(f"Binding '{self.id}': Changing from '{old}' to '{new}'")
|
||||
self.ft = self._update.update(self.ft, self.ft_name, self.ft_attr, old, new)
|
||||
self.ft = self._update.update(self.ft, self.ft_name, self.ft_attr, old, new, self.data_converter)
|
||||
|
||||
self.ft.attrs["hx-swap-oob"] = "true"
|
||||
return self.ft
|
||||
|
||||
def update(self, values: dict):
|
||||
"""
|
||||
Called by the FastHTML router when a request is received.
|
||||
:param values:
|
||||
:return: the list of updated elements (all elements that are bound to this binding)
|
||||
"""
|
||||
logger.debug(f"Binding '{self.id}': Updating with {values=}.")
|
||||
matches, value = self._detection.matches(values)
|
||||
if matches:
|
||||
setattr(self.data, self.data_attr, self.data_converter.convert(value) if self.data_converter else value)
|
||||
setattr(self.data, self.data_attr, value)
|
||||
res = collect_return_values(self.data)
|
||||
return res
|
||||
|
||||
|
||||
Reference in New Issue
Block a user