diff --git a/examples/PlainPython/Menu/README.md b/examples/PlainPython/Menu/README.md new file mode 100644 index 00000000..7458449a --- /dev/null +++ b/examples/PlainPython/Menu/README.md @@ -0,0 +1,20 @@ +# Menu example + +This example illustrates how to implement a dropdown menu in vuetify using `v-slot:activator` with `v-bind` and `v-on` like this one: + +```html + +``` + +And demonstrate how to pass variables from Vue to a Python method. diff --git a/examples/PlainPython/Menu/app.py b/examples/PlainPython/Menu/app.py new file mode 100644 index 00000000..d9eac957 --- /dev/null +++ b/examples/PlainPython/Menu/app.py @@ -0,0 +1,31 @@ +from trame import state +from trame.html import vuetify +from trame.layouts import SinglePage + +layout = SinglePage("Menu example") + +state.menu_items = ["one", "two", "three"] + + +def print_item(item): + print("Clicked on", item) + + +with layout.toolbar: + vuetify.VSpacer() + with vuetify.VMenu(): + with vuetify.Template(v_slot_activator="{ on, attrs }"): + with vuetify.VBtn(icon=True, v_bind="attrs", v_on="on", v_on_click="test"): + vuetify.VIcon("mdi-dots-vertical") + with vuetify.VList(), vuetify.VListItem( + v_for="(item, i) in menu_items", + key="i", + value=["item"], + ): + vuetify.VBtn( + "{{ item }}", + click=(print_item, "[item]"), + ) + +if __name__ == "__main__": + layout.start() diff --git a/trame/html/__init__.py b/trame/html/__init__.py index 4a1232e8..6c7f0dca 100644 --- a/trame/html/__init__.py +++ b/trame/html/__init__.py @@ -15,6 +15,35 @@ def py2js_key(key): return key.replace("_", "-") +def js2py_key(key): + return key.replace("-", "_") + + +def build_attr_names(name_prefix, key_names, kwargs): + """Used to generate a list of attr_names with a common name_prefix.""" + attr_names = [] + for key_name in key_names: + safe_name_prefix = js2py_key(name_prefix) + safe_name = js2py_key(key_name).replace(".", "_") + if "" in safe_name: + safe_header, safe_tail = safe_name.split("") + header, tail = key_name.split("") + for key in kwargs: + if key.startswith(header): + dyna_name = key[len(header) : -len(tail)] + attr_names.append( + ( + f"{safe_name_prefix}_{safe_header}{dyna_name}{safe_tail}", + f"{name_prefix}:{header}{dyna_name}{tail}", + ) + ) + else: + attr_names.append( + (f"{safe_name_prefix}_{safe_name}", f"{name_prefix}:{key_name}") + ) + return attr_names + + class ElementContextManager: def __init__(self): self.element_stack = [] @@ -33,6 +62,36 @@ def add_child(self, elem): HTML_CTX = ElementContextManager() +key_names = [ + "delete", + "down", + "enter", + "esc", + "left", + "right", + "space", + "tab", + "up", +] +v_on_names = [ + "click.capture", + "click.once", + "click.prevent", + "click.prevent.self", + "click.self.prevent", + "click.self", + "click.stop.prevent", + "click.stop", + "click", + "scroll.passive", + "scroll", + "submit.prevent", + "submit", + *["keyup." + k for k in key_names], + *["keydown." + k for k in key_names], +] +v_bind_names = ["class", "style"] + class AbstractElement: """ @@ -72,6 +131,8 @@ class AbstractElement: :param v_if: See |vue_doc_link| for more info :param v_show: See |vue_doc_link| for more info :param v_for: See |vue_doc_link| for more info + :param v_on: See |vue_doc_link| for more info + :param v_bind: See |vue_doc_link| for more info :param key: See |vue_doc_link| for more info Events - See |mdn_event_link| for more info @@ -113,17 +174,21 @@ def __init__(self, _elem_name, children=None, **kwargs): "style", ("key", ":key"), # default vue.js directives - "v_text", - "v_html", - "v_show", - "v_if", - "v_else", + "v_bind", "v_else_if", + "v_else", "v_for", + "v_html", + "v_if", "v_model", - "v_pre", + "v_on", "v_once", + "v_pre", + "v_show", + "v_text", ] + self._attr_names += build_attr_names("v-on", v_on_names, kwargs) + self._attr_names += build_attr_names("v-bind", v_bind_names, kwargs) self._event_names += [ "click", "mousedown", @@ -505,22 +570,7 @@ class Template(AbstractElement): def __init__(self, children=None, **kwargs): super().__init__("template", children, **kwargs) self._attr_names += ["v_slot"] - for slot_name in Template.slot_names: - safe_name = slot_name.replace("-", "_").replace(".", "_") - if "" in safe_name: - safe_header, safe_tail = safe_name.split("") - header, tail = slot_name.split("") - for key in kwargs: - if key.startswith(header): - dyna_name = key[len(header) : -len(tail)] - self._attr_names.append( - ( - f"v_slot_{safe_header}{dyna_name}{safe_tail}", - f"v-slot:{header}{dyna_name}{tail}", - ) - ) - else: - self._attr_names.append((f"v_slot_{safe_name}", f"v-slot:{slot_name}")) + self._attr_names += build_attr_names("v-slot", Template.slot_names, kwargs) class StateChange(AbstractElement):