diff --git a/solara/components/component_vue.py b/solara/components/component_vue.py
index 48c04a4d0..b2975df94 100644
--- a/solara/components/component_vue.py
+++ b/solara/components/component_vue.py
@@ -12,11 +12,23 @@
P = typing_extensions.ParamSpec("P")
-def _widget_from_signature(name, base_class: Type[widgets.Widget], func: Callable[..., None]) -> Type[widgets.Widget]:
- traits = {}
+def _widget_from_signature(classname, base_class: Type[widgets.Widget], func: Callable[..., None], event_prefix: str) -> Type[widgets.Widget]:
+ classprops = {}
parameters = inspect.signature(func).parameters
for name, param in parameters.items():
+ if name.startswith("event_"):
+ event_name = name[6:]
+
+ def event_handler(self, data, buffers=None, event_name=event_name):
+ callback = self._event_callbacks.get(event_name)
+ if callback:
+ if buffers:
+ callback(data, buffers)
+ else:
+ callback(data)
+
+ classprops[f"vue_{event_name}"] = event_handler
if name.startswith("on_") and name[3:] in parameters:
# callback, will be handled by reacton
continue
@@ -24,8 +36,11 @@ def _widget_from_signature(name, base_class: Type[widgets.Widget], func: Callabl
trait = traitlets.Any()
else:
trait = traitlets.Any(default_value=param.default)
- traits[name] = trait.tag(sync=True, **widgets.widget_serialization)
- widget_class = type(name, (base_class,), traits)
+ classprops[name] = trait.tag(sync=True, **widgets.widget_serialization)
+ # maps event_foo to a callable
+ classprops["_event_callbacks"] = traitlets.Dict(default_value={})
+
+ widget_class = type(classname, (base_class,), classprops)
return widget_class
@@ -38,7 +53,7 @@ class VueWidgetSolara(vue.VueTemplate):
template_file = (inspect.getfile(func), vue_path)
base_class = VuetifyWidgetSolara if vuetify else VueWidgetSolara
- widget_class = _widget_from_signature("VueWidgetSolaraSub", base_class, func)
+ widget_class = _widget_from_signature("VueWidgetSolaraSub", base_class, func, "vue_")
return widget_class
@@ -57,6 +72,9 @@ def component_vue(vue_path: str, vuetify=True) -> Callable[[Callable[P, None]],
are assumed by refer to the same vue property, with `on_foo` being the event handler when `foo` changes from
the vue template.
+ Arguments or the form `event_foo` should be callbacks that can be called from the vue template. They are
+ available as the function `foo` in the vue template.
+
[See the vue v2 api](https://v2.vuejs.org/v2/api/) for more information on how to use Vue, like `watch`,
`methods` and lifecycle hooks such as `mounted` and `destroyed`.
@@ -73,6 +91,14 @@ def decorator(func: Callable[P, None]):
VueWidgetSolaraSub = _widget_vue(vue_path, vuetify=vuetify)(func)
def wrapper(*args, **kwargs):
+ event_callbacks = {}
+ kwargs = kwargs.copy()
+ # take out all events named like event_foo and put them in a separate dict
+ for name in list(kwargs):
+ if name.startswith("event_"):
+ event_callbacks[name[6:]] = kwargs.pop(name)
+ if event_callbacks:
+ kwargs["_event_callbacks"] = event_callbacks
return VueWidgetSolaraSub.element(*args, **kwargs) # type: ignore
return wrapper
diff --git a/solara/website/pages/examples/general/mycard.vue b/solara/website/pages/examples/general/mycard.vue
index d927e54cb..f68d47b05 100644
--- a/solara/website/pages/examples/general/mycard.vue
+++ b/solara/website/pages/examples/general/mycard.vue
@@ -18,7 +18,7 @@
-
+
Go to Report
diff --git a/solara/website/pages/examples/general/vue_component.py b/solara/website/pages/examples/general/vue_component.py
index e3e05cb4e..6aa5cf011 100644
--- a/solara/website/pages/examples/general/vue_component.py
+++ b/solara/website/pages/examples/general/vue_component.py
@@ -11,6 +11,8 @@
"""
+from typing import Callable
+
import numpy as np
import solara
@@ -20,6 +22,7 @@
@solara.component_vue("mycard.vue")
def MyCard(
+ event_goto_report: Callable[[dict], None],
value=[1, 10, 30, 20, 3],
caption="My Card",
color="red",
@@ -31,11 +34,18 @@ def MyCard(
def Page():
gen = np.random.RandomState(seed=seed.value)
sales_data = np.floor(np.cumsum(gen.random(7) - 0.5) * 100 + 100)
+ show_report = solara.use_reactive(False)
+
with solara.Column(style={"min-width": "600px"}):
+ if show_report.value:
+ with solara.Card("Report"):
+ solara.Markdown("Lorum ipsum dolor sit amet")
+ solara.Button("Go back", on_click=lambda: show_report.set(False))
+ else:
- def new_seed():
- seed.value = np.random.randint(0, 100)
+ def new_seed():
+ seed.value = np.random.randint(0, 100)
- solara.Button("Generate new data", on_click=new_seed)
+ solara.Button("Generate new data", on_click=new_seed)
- MyCard(value=sales_data.tolist(), color="green", caption="Sales Last 7 Days")
+ MyCard(value=sales_data.tolist(), color="green", caption="Sales Last 7 Days", event_goto_report=lambda data: show_report.set(True))
diff --git a/tests/unit/component_frontend_test.py b/tests/unit/component_frontend_test.py
index 395510530..4f39a0287 100644
--- a/tests/unit/component_frontend_test.py
+++ b/tests/unit/component_frontend_test.py
@@ -32,3 +32,19 @@ def ComponentVueTest(value: int, on_value=None):
mock.assert_called_once_with(2)
widget.value = 3
mock.assert_called_with(3)
+
+
+def test_component_vue_event():
+ mock = unittest.mock.Mock()
+
+ @solara._component_vue("component_vue_test.vue")
+ def ComponentVueTest(event_foo=None):
+ pass
+
+ box, rc = solara.render(ComponentVueTest(event_foo=mock), handle_error=False)
+ widget = box.children[0]
+ mock.assert_not_called()
+ widget._handle_event(None, {"event": "foo", "data": 42}, None)
+ mock.assert_called_once_with(42)
+ widget._handle_event(None, {"event": "foo", "data": 42}, [b"bar"])
+ mock.assert_called_with(42, [b"bar"])