1
0
Falko Schindler 2 жил өмнө
parent
commit
d7e0fa5d9f

+ 19 - 15
nicegui/element.py

@@ -1,6 +1,7 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
 import re
 import re
+import warnings
 from copy import deepcopy
 from copy import deepcopy
 from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
 from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
 
 
@@ -38,7 +39,7 @@ class Element(Visibility):
         self._classes: List[str] = []
         self._classes: List[str] = []
         self._style: Dict[str, str] = {}
         self._style: Dict[str, str] = {}
         self._props: Dict[str, Any] = {}
         self._props: Dict[str, Any] = {}
-        self._event_listeners: List[EventListener] = []
+        self._event_listeners: Dict[str, EventListener] = {}
         self._text: str = ''
         self._text: str = ''
         self.slots: Dict[str, Slot] = {}
         self.slots: Dict[str, Slot] = {}
         self.default_slot = self.add_slot('default')
         self.default_slot = self.add_slot('default')
@@ -71,23 +72,24 @@ class Element(Visibility):
     def __exit__(self, *_):
     def __exit__(self, *_):
         self.default_slot.__exit__(*_)
         self.default_slot.__exit__(*_)
 
 
-    def _collect_event_dict(self) -> Dict[str, Dict]:
-        events: Dict[str, Dict] = {}
-        for listener in self._event_listeners:
+    def _collect_events(self) -> List[Dict]:
+        events: List[Dict] = []
+        for listener in self._event_listeners.values():
             words = listener.type.split('.')
             words = listener.type.split('.')
             type = words.pop(0)
             type = words.pop(0)
             specials = [w for w in words if w in {'capture', 'once', 'passive'}]
             specials = [w for w in words if w in {'capture', 'once', 'passive'}]
             modifiers = [w for w in words if w in {'stop', 'prevent', 'self', 'ctrl', 'shift', 'alt', 'meta'}]
             modifiers = [w for w in words if w in {'stop', 'prevent', 'self', 'ctrl', 'shift', 'alt', 'meta'}]
             keys = [w for w in words if w not in specials + modifiers]
             keys = [w for w in words if w not in specials + modifiers]
-            events[listener.type] = {
+            events.append({
+                'listener_id': listener.id,
                 'listener_type': listener.type,
                 'listener_type': listener.type,
                 'type': type,
                 'type': type,
                 'specials': specials,
                 'specials': specials,
                 'modifiers': modifiers,
                 'modifiers': modifiers,
                 'keys': keys,
                 'keys': keys,
-                'args': list(set(events.get(listener.type, {}).get('args', []) + listener.args)),
-                'throttle': min(events.get(listener.type, {}).get('throttle', float('inf')), listener.throttle),
-            }
+                'args': listener.args,
+                'throttle': listener.throttle,
+            })
         return events
         return events
 
 
     def _collect_slot_dict(self) -> Dict[str, List[int]]:
     def _collect_slot_dict(self) -> Dict[str, List[int]]:
@@ -106,7 +108,7 @@ class Element(Visibility):
                 'props': self._props,
                 'props': self._props,
                 'text': self._text,
                 'text': self._text,
                 'slots': self._collect_slot_dict(),
                 'slots': self._collect_slot_dict(),
-                'events': self._collect_event_dict(),
+                'events': self._collect_events(),
             }
             }
         dict_: Dict[str, Any] = {}
         dict_: Dict[str, Any] = {}
         for key in keys:
         for key in keys:
@@ -125,7 +127,7 @@ class Element(Visibility):
             elif key == 'slots':
             elif key == 'slots':
                 dict_['slots'] = self._collect_slot_dict()
                 dict_['slots'] = self._collect_slot_dict()
             elif key == 'events':
             elif key == 'events':
-                dict_['events'] = self._collect_event_dict()
+                dict_['events'] = self._collect_events()
             else:
             else:
                 raise ValueError(f'Unknown key {key}')
                 raise ValueError(f'Unknown key {key}')
         return dict_
         return dict_
@@ -237,15 +239,17 @@ class Element(Visibility):
         :param throttle: minimum time (in seconds) between event occurrences (default: 0.0)
         :param throttle: minimum time (in seconds) between event occurrences (default: 0.0)
         """
         """
         if handler:
         if handler:
-            args = args if args is not None else ['*']
+            if args and '*' in args:
+                url = f'https://github.com/zauberzeug/nicegui/issues/644'
+                warnings.warn(DeprecationWarning(f'Event args "*" is deprecated, omit this parameter instead ({url})'))
+                args = None
             listener = EventListener(element_id=self.id, type=type, args=args, handler=handler, throttle=throttle)
             listener = EventListener(element_id=self.id, type=type, args=args, handler=handler, throttle=throttle)
-            self._event_listeners.append(listener)
+            self._event_listeners[listener.id] = listener
         return self
         return self
 
 
     def _handle_event(self, msg: Dict) -> None:
     def _handle_event(self, msg: Dict) -> None:
-        for listener in self._event_listeners:
-            if listener.type == msg['type']:
-                events.handle_event(listener.handler, msg, sender=self)
+        listener = self._event_listeners[msg['listener_id']]
+        events.handle_event(listener.handler, msg, sender=self)
 
 
     def update(self) -> None:
     def update(self) -> None:
         """Update the element on the client side."""
         """Update the element on the client side."""

+ 6 - 1
nicegui/event_listener.py

@@ -1,11 +1,16 @@
-from dataclasses import dataclass
+import uuid
+from dataclasses import dataclass, field
 from typing import Callable, List
 from typing import Callable, List
 
 
 
 
 @dataclass
 @dataclass
 class EventListener:
 class EventListener:
+    id: str = field(init=False)
     element_id: int
     element_id: int
     type: str
     type: str
     args: List[str]
     args: List[str]
     handler: Callable
     handler: Callable
     throttle: float
     throttle: float
+
+    def __post_init__(self) -> None:
+        self.id = str(uuid.uuid4())

+ 9 - 5
nicegui/templates/index.html

@@ -51,21 +51,25 @@
           style: Object.entries(element.style).reduce((str, [p, val]) => `${str}${p}:${val};`, '') || undefined,
           style: Object.entries(element.style).reduce((str, [p, val]) => `${str}${p}:${val};`, '') || undefined,
           ...element.props,
           ...element.props,
         };
         };
-        Object.values(element.events).forEach((event) => {
+        element.events.forEach((event) => {
           let event_name = 'on' + event.type[0].toLocaleUpperCase() + event.type.substring(1);
           let event_name = 'on' + event.type[0].toLocaleUpperCase() + event.type.substring(1);
           event.specials.forEach(s => event_name += s[0].toLocaleUpperCase() + s.substring(1));
           event.specials.forEach(s => event_name += s[0].toLocaleUpperCase() + s.substring(1));
           let handler = (e) => {
           let handler = (e) => {
-            const all = typeof e !== 'object' || event.args.includes('*');
+            const all = typeof e !== 'object' || !event.args;
             const args = all ? e : Object.fromEntries(event.args.map(a => [a, e[a]]));
             const args = all ? e : Object.fromEntries(event.args.map(a => [a, e[a]]));
-            const emitter = () => window.socket.emit("event", {id: element.id, type: event.listener_type, args});
-            throttle(emitter, event.throttle, event.listener_type);
+            const emitter = () => window.socket.emit("event", {id: element.id, listener_id: event.listener_id, args});
+            throttle(emitter, event.throttle, event.listener_id);
             if (element.props["loopback"] === False && event.type == "update:model-value") {
             if (element.props["loopback"] === False && event.type == "update:model-value") {
               element.props["model-value"] = args;
               element.props["model-value"] = args;
             }
             }
           };
           };
           handler = Vue.withModifiers(handler, event.modifiers);
           handler = Vue.withModifiers(handler, event.modifiers);
           handler = event.keys.length ? Vue.withKeys(handler, event.keys) : handler;
           handler = event.keys.length ? Vue.withKeys(handler, event.keys) : handler;
-          props[event_name] = handler;
+          if (props[event_name]) {
+            props[event_name].push(handler)
+          } else {
+            props[event_name] = [handler];
+          }
         });
         });
         const slots = {};
         const slots = {};
         Object.entries(element.slots).forEach(([name, data]) => {
         Object.entries(element.slots).forEach(([name, data]) => {