Ver código fonte

let `run_method` and `call_*_method` return AwaitableResponse

Falko Schindler 1 ano atrás
pai
commit
bdcac465fc

+ 9 - 0
nicegui/awaitable_response.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from typing import Callable
 
 from . import background_tasks
@@ -22,3 +24,10 @@ class AwaitableResponse:
     def __await__(self):
         self.fire_and_forget_task.cancel()
         return self.wait_for_result().__await__()
+
+    @staticmethod
+    def none() -> AwaitableResponse:
+        """Return an AwaitableResponse that does nothing."""
+        async def do_nothing():
+            return None
+        return AwaitableResponse(lambda: None, do_nothing)

+ 14 - 6
nicegui/element.py

@@ -9,7 +9,8 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional,
 
 from typing_extensions import Self
 
-from . import events, globals, outbox, storage  # pylint: disable=redefined-builtin
+from . import events, globals, json, outbox, storage  # pylint: disable=redefined-builtin
+from .awaitable_response import AwaitableResponse
 from .dependencies import Component, Library, register_library, register_vue_component
 from .elements.mixins.visibility import Visibility
 from .event_listener import EventListener
@@ -402,17 +403,24 @@ class Element(Visibility):
         """Update the element on the client side."""
         outbox.enqueue_update(self)
 
-    def run_method(self, name: str, *args: Any) -> None:
+    def run_method(self, name: str, *args: Any) -> AwaitableResponse:
         """Run a method on the client side.
 
         :param name: name of the method
         :param args: arguments to pass to the method
         """
         if not globals.loop:
-            return
-        data = {'id': self.id, 'name': name, 'args': args}
-        target_id = globals._socket_id or self.client.id  # pylint: disable=protected-access
-        outbox.enqueue_message('run_method', data, target_id)
+            return AwaitableResponse.none()  # TODO: raise exception instead?
+        args_string = json.dumps(args)
+        return self.client.run_javascript(f'''
+              const element = getElement("{self.id}");
+              if (element === null || element === undefined) return;
+              if ("{name}" in element) {{
+                element["{name}"](...{args_string});
+              }} else {{
+                element.$refs.qRef["{name}"](...{args_string});
+              }}
+        ''')  # TODO: consider globals._socket_id
 
     def _collect_descendants(self, *, include_self: bool = False) -> List[Element]:
         elements: List[Element] = [self] if include_self else []

+ 2 - 2
nicegui/elements/aggrid.js

@@ -48,10 +48,10 @@ export default {
       this.gridOptions.api.addGlobalListener(this.handle_event);
     },
     call_api_method(name, ...args) {
-      this.gridOptions.api[name](...args);
+      return this.gridOptions.api[name](...args);
     },
     call_column_api_method(name, ...args) {
-      this.gridOptions.columnApi[name](...args);
+      return this.gridOptions.columnApi[name](...args);
     },
     handle_event(type, args) {
       this.$emit(type, {

+ 7 - 7
nicegui/elements/aggrid.py

@@ -3,8 +3,8 @@ from __future__ import annotations
 from typing import Dict, List, Optional, cast
 
 from .. import globals  # pylint: disable=redefined-builtin
+from ..awaitable_response import AwaitableResponse
 from ..element import Element
-from ..functions.javascript import run_javascript
 
 try:
     import pandas as pd
@@ -84,7 +84,7 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
         super().update()
         self.run_method('update_grid')
 
-    def call_api_method(self, name: str, *args) -> None:
+    def call_api_method(self, name: str, *args) -> AwaitableResponse:
         """Call an AG Grid API method.
 
         See `AG Grid API <https://www.ag-grid.com/javascript-data-grid/grid-api/>`_ for a list of methods.
@@ -92,9 +92,9 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
         :param name: name of the method
         :param args: arguments to pass to the method
         """
-        self.run_method('call_api_method', name, *args)
+        return self.run_method('call_api_method', name, *args)
 
-    def call_column_api_method(self, name: str, *args) -> None:
+    def call_column_api_method(self, name: str, *args) -> AwaitableResponse:
         """Call an AG Grid Column API method.
 
         See `AG Grid Column API <https://www.ag-grid.com/javascript-data-grid/column-api/>`_ for a list of methods.
@@ -102,7 +102,7 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
         :param name: name of the method
         :param args: arguments to pass to the method
         """
-        self.run_method('call_column_api_method', name, *args)
+        return self.run_method('call_column_api_method', name, *args)
 
     async def get_selected_rows(self) -> List[Dict]:
         """Get the currently selected rows.
@@ -113,7 +113,7 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
 
         :return: list of selected row data
         """
-        result = await run_javascript(f'return getElement({self.id}).gridOptions.api.getSelectedRows();')
+        result = await self.client.run_javascript(f'return getElement({self.id}).gridOptions.api.getSelectedRows();')
         return cast(List[Dict], result)
 
     async def get_selected_row(self) -> Optional[Dict]:
@@ -138,7 +138,7 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
 
         :return: list of row data
         """
-        result = await run_javascript(f'''
+        result = await self.client.run_javascript(f'''
             const rowData = [];
             getElement({self.id}).gridOptions.api.forEachNode(node => rowData.push(node.data));
             return rowData;

+ 4 - 3
nicegui/elements/scene.py

@@ -4,6 +4,7 @@ from typing import Any, Callable, Dict, List, Optional, Union
 from typing_extensions import Self
 
 from .. import binding, globals  # pylint: disable=redefined-builtin
+from ..awaitable_response import AwaitableResponse
 from ..dataclasses import KWONLY_SLOTS
 from ..element import Element
 from ..events import (GenericEventArguments, SceneClickEventArguments, SceneClickHit, SceneDragEventArguments,
@@ -116,15 +117,15 @@ class Scene(Element,
             for obj in self.objects.values():
                 obj.send()
 
-    def run_method(self, name: str, *args: Any) -> None:
+    def run_method(self, name: str, *args: Any) -> AwaitableResponse:
         """Run a method on the client.
 
         :param name: name of the method
         :param args: arguments to pass to the method
         """
         if not self.is_initialized:
-            return
-        super().run_method(name, *args)
+            return AwaitableResponse.none()  # TODO: raise exception instead?
+        return super().run_method(name, *args)
 
     def _handle_click(self, e: GenericEventArguments) -> None:
         arguments = SceneClickEventArguments(

+ 0 - 9
nicegui/templates/index.html

@@ -277,15 +277,6 @@
                 this.elements[element.id] = element;
               }
             },
-            run_method: (msg) => {
-              const element = getElement(msg.id);
-              if (element === null || element === undefined) return;
-              if (msg.name in element) {
-                element[msg.name](...msg.args);
-              } else {
-                element.$refs.qRef[msg.name](...msg.args);
-              }
-            },
             run_javascript: (msg) => runJavascript(msg['code'], msg['request_id']),
             open: (msg) => {
               const url = msg.path.startsWith('/') ? "{{ prefix | safe }}" + msg.path : msg.path;