Skip to content

Commit 942cbe3

Browse files
committed
feat(v_on): better handling events modifiers
1 parent 8e3e204 commit 942cbe3

3 files changed

Lines changed: 145 additions & 141 deletions

File tree

examples/vue3/v_on.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from trame_client.widgets import html
2+
from trame.app import get_server
3+
4+
server = get_server()
5+
print(html.Div(trame_server=server, v_on_click_left_stop_self="hello"))

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ authors = [
66
{name = "Kitware Inc."},
77
]
88
dependencies = [
9+
"trame-common>=0.2.0",
910
]
1011
requires-python = ">=3.9"
1112
readme = "README.rst"

trame_client/widgets/core.py

Lines changed: 139 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import sys
22
import logging
3-
import inspect
43

5-
from ..utils.defaults import TrameDefault
6-
from ..utils.formatter import to_pretty_html
4+
from trame_client.utils.defaults import TrameDefault
5+
from trame_client.utils.formatter import to_pretty_html
6+
from trame_common.obj.component import TrameComponent
77

88
AVAILABLE_DIRECTIVES = [
99
("v_text", "v-text"),
@@ -25,6 +25,74 @@
2525
("v_memo", "v-memo"),
2626
("v_cloak", "v-cloak"),
2727
]
28+
KEY_ALIAS = [
29+
"enter",
30+
"tab",
31+
"delete",
32+
"esc",
33+
"space",
34+
"left",
35+
"up",
36+
"right",
37+
"down",
38+
]
39+
KEY_MODIFIER = ["ctrl", "alt", "shift", "meta"]
40+
MOUSE_BUTTONS = ["left", "right", "middle"]
41+
V_ON_MODIFIER = [
42+
"stop",
43+
"prevent",
44+
"capture",
45+
"self",
46+
"once",
47+
"left",
48+
"right",
49+
"middle",
50+
"passive",
51+
] # *KEY_ALIAS
52+
V_ON_TYPE_MOUSE = [
53+
"click",
54+
"dblclick",
55+
"mousedown",
56+
"mouseup",
57+
"mousemove",
58+
"mouseover",
59+
"mouseout",
60+
"mousewheel",
61+
]
62+
V_ON_TYPE_KEYBOARD = [
63+
"keydown",
64+
"keyup",
65+
"keypress",
66+
]
67+
V_ON_TYPE_FORM = [
68+
"submit",
69+
"input",
70+
"change",
71+
"focus",
72+
"blur",
73+
]
74+
V_ON_TYPE_TOUCH = [
75+
"touchstart",
76+
"touchmove",
77+
"touchend",
78+
"touchcancel",
79+
]
80+
V_ON_TYPE_UI = [
81+
"scroll",
82+
"resize",
83+
"select",
84+
]
85+
V_ON_TYPE_ANIM = [
86+
"animationstart",
87+
"animationend",
88+
"animationiteration",
89+
]
90+
V_ON_TYPE_TRANSITION = [
91+
"transitionstart",
92+
"transitionend",
93+
"transitioncancel",
94+
]
95+
2896

2997
SHARED_ATTRIBUTES = [
3098
"accesskey",
@@ -57,22 +125,21 @@
57125
["key", ":key"],
58126
]
59127

128+
# !all modifiers should go through v_on_click_left_stop...
60129
SHARED_EVENTS = [
61-
"click",
62-
"mousedown",
63-
"mouseup",
64-
"mouseenter",
65-
"mouseleave",
130+
*V_ON_TYPE_MOUSE,
131+
*V_ON_TYPE_KEYBOARD,
132+
*V_ON_TYPE_FORM,
133+
*V_ON_TYPE_TOUCH,
134+
*V_ON_TYPE_UI,
135+
*V_ON_TYPE_ANIM,
136+
*V_ON_TYPE_TRANSITION,
66137
"contextmenu",
67138
]
68139

69140
logger = logging.getLogger(__name__)
70141

71142

72-
def can_be_decorated(x):
73-
return inspect.ismethod(x) or inspect.isfunction(x)
74-
75-
76143
def py2js_key(key):
77144
return key.replace("_", "-")
78145

@@ -217,99 +284,33 @@ def __call__(self, layout=None, **kwargs):
217284
HTML_CTX.add_child(self)
218285

219286

220-
class TrameComponent:
221-
"""
222-
Base trame class that has access to a trame server instance
223-
on which we provide simple accessor and method decoration capabilities.
224-
"""
225-
226-
def __init__(self, server, ctx_name=None, **_):
227-
"""
228-
Initialize TrameComponent with its server.
229-
230-
Keyword arguments:
231-
server -- the server to link to (default None)
232-
ctx_name -- name to use to bind current instance to server.context (default None)
233-
"""
234-
self._server = server
235-
236-
if ctx_name:
237-
self.ctx[ctx_name] = self
238-
239-
self._bind_annotated_methods()
240-
241-
@property
242-
def server(self):
243-
"""Return the associated trame server instance"""
244-
return self._server
245-
246-
@property
247-
def state(self):
248-
"""Return the associated server state"""
249-
return self.server.state
250-
251-
@property
252-
def ctrl(self):
253-
"""Return the associated server controller"""
254-
return self.server.controller
255-
256-
@property
257-
def ctx(self):
258-
"""Return the associated server context"""
259-
return self.server.context
260-
261-
def _bind_annotated_methods(self):
262-
# Look for method decorator
263-
for k in inspect.getmembers(self.__class__, can_be_decorated):
264-
fn = getattr(self, k[0])
265-
266-
# Handle @state.change
267-
s_translator = self.state.translator
268-
if "_trame_state_change" in fn.__dict__:
269-
state_change_names = fn.__dict__["_trame_state_change"]
270-
logger.debug(
271-
f"state.change({[f'{s_translator.translate_key(v)}' for v in state_change_names]})({k[0]})"
272-
)
273-
self.state.change(*[f"{v}" for v in state_change_names])(fn)
274-
275-
# Handle @trigger
276-
if "_trame_trigger_names" in fn.__dict__:
277-
trigger_names = fn.__dict__["_trame_trigger_names"]
278-
for trigger_name in trigger_names:
279-
logger.debug(f"trigger({trigger_name})({k[0]})")
280-
self.server.trigger(f"{trigger_name}")(fn)
281-
282-
# Handle @ctrl.[add, once, add_task, set]
283-
if "_trame_controller" in fn.__dict__:
284-
actions = fn.__dict__["_trame_controller"]
285-
for action in actions:
286-
name = action.get("name")
287-
method = action.get("method")
288-
decorate = getattr(self.ctrl, method)
289-
logger.debug(f"ctrl.{method}({name})({k[0]})")
290-
decorate(name)(fn)
291-
292-
def _unbind_annotated_methods(self):
293-
# Look for method decorator
294-
for k in inspect.getmembers(self.__class__, can_be_decorated):
295-
fn = getattr(self, k[0])
296-
297-
# Handle @state.change
298-
methods_to_detach = {}
299-
if "_trame_state_change" in fn.__dict__:
300-
methods_to_detach.add(fn)
301-
302-
if methods_to_detach:
303-
for fn_list in self.state._change_callbacks.values():
304-
to_remove = set(fn_list) | methods_to_detach
305-
for fn in to_remove:
306-
fn_list.remove(fn)
307-
308-
# Handle @trigger
309-
# TODO
310-
311-
# Handle @ctrl
312-
# TODO
287+
def _event_value_processing(server, js_key, value):
288+
if isinstance(value, str):
289+
translated_value = server.state.translator.translate_js_expression(
290+
server.state, value
291+
)
292+
return f'{js_key}="{translated_value}"'
293+
elif callable(value):
294+
trigger_name = server.trigger_name(value)
295+
return f"{js_key}=\"trigger('{trigger_name}')\""
296+
elif isinstance(value, tuple):
297+
trigger_name = value[0]
298+
if callable(trigger_name):
299+
trigger_name = server.trigger_name(trigger_name)
300+
if len(value) == 1:
301+
return f"{js_key}=\"trigger('{trigger_name}')\""
302+
if len(value) == 2:
303+
translated_value = server.state.translator.translate_js_expression(
304+
server.state, value[1]
305+
)
306+
return f"{js_key}=\"trigger('{trigger_name}', {translated_value})\""
307+
if len(value) == 3:
308+
translated_value = server.state.translator.translate_js_expression(
309+
server.state, value[1]
310+
)
311+
# We don't want to translate kwargs as we may change keys rather than just values
312+
return f"{js_key}=\"trigger('{trigger_name}', {translated_value}, {value[2]})\""
313+
return False
313314

314315

315316
class AbstractElement(TrameComponent):
@@ -608,6 +609,7 @@ def events(self, *names):
608609
:param names: The names events to process
609610
:type names: *str
610611
"""
612+
processed_event = set()
611613
for _name in names:
612614
js_key = None
613615
name = _name
@@ -623,48 +625,44 @@ def events(self, *names):
623625
if value is None:
624626
continue
625627

626-
if isinstance(value, str):
627-
translated_value = (
628-
self.server.state.translator.translate_js_expression(
629-
self.server.state, value
630-
)
628+
attribute = _event_value_processing(self.server, js_key, value)
629+
if attribute is None:
630+
# no match
631+
pass
632+
elif isinstance(attribute, str):
633+
self._attributes[name] = attribute
634+
processed_event.add(name)
635+
else:
636+
print(
637+
"Error: Don't know how to handle event name "
638+
f"'{name}' with value '{value}' in {self.__class__}::{self._elem_name}"
631639
)
632-
self._attributes[name] = f'{js_key}="{translated_value}"'
633-
elif callable(value):
634-
trigger_name = self.server.trigger_name(value)
635-
self._attributes[name] = f"{js_key}=\"trigger('{trigger_name}')\""
636-
elif isinstance(value, tuple):
637-
trigger_name = value[0]
638-
if callable(trigger_name):
639-
trigger_name = self.server.trigger_name(trigger_name)
640-
if len(value) == 1:
641-
self._attributes[name] = (
642-
f"{js_key}=\"trigger('{trigger_name}')\""
643-
)
644-
if len(value) == 2:
645-
translated_value = (
646-
self.server.state.translator.translate_js_expression(
647-
self.server.state, value[1]
648-
)
649-
)
650-
self._attributes[name] = (
651-
f"{js_key}=\"trigger('{trigger_name}', {translated_value})\""
652-
)
653-
if len(value) == 3:
654-
translated_value = (
655-
self.server.state.translator.translate_js_expression(
656-
self.server.state, value[1]
657-
)
658-
)
659-
# We don't want to translate kwargs as we may change keys rather than just values
660-
self._attributes[name] = (
661-
f"{js_key}=\"trigger('{trigger_name}', {translated_value}, {value[2]})\""
662-
)
640+
641+
# process v_on_....
642+
for key_name in self._py_attr:
643+
if key_name in processed_event:
644+
continue
645+
646+
if key_name.startswith("v_on_"):
647+
tokens = key_name.split("_")[2:]
648+
js_key = f"@{'.'.join(tokens)}"
649+
value = self._py_attr[key_name]
650+
651+
if value is None:
652+
continue
653+
654+
attribute = _event_value_processing(self.server, js_key, value)
655+
if attribute is None:
656+
# no match
657+
pass
658+
elif isinstance(attribute, str):
659+
self._attributes[name] = attribute
663660
else:
664661
print(
665662
"Error: Don't know how to handle event name "
666663
f"'{name}' with value '{value}' in {self.__class__}::{self._elem_name}"
667664
)
665+
668666
return self
669667

670668
def clear(self):

0 commit comments

Comments
 (0)