浏览代码

Merge pull request #2162 from natankeddem/json_editor_arb_method

Add Dynamic Method Call to JSON Editor
Falko Schindler 1 年之前
父节点
当前提交
d12a8e968b

+ 2 - 2
nicegui/elements/aggrid.js

@@ -47,10 +47,10 @@ export default {
       this.grid = new agGrid.Grid(this.$el, this.gridOptions);
       this.gridOptions.api.addGlobalListener(this.handle_event);
     },
-    call_api_method(name, ...args) {
+    run_grid_method(name, ...args) {
       return this.gridOptions.api[name](...args);
     },
-    call_column_api_method(name, ...args) {
+    run_column_method(name, ...args) {
       return this.gridOptions.columnApi[name](...args);
     },
     handle_event(type, args) {

+ 16 - 8
nicegui/elements/aggrid.py

@@ -25,7 +25,7 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
 
         An element to create a grid using `AG Grid <https://www.ag-grid.com/>`_.
 
-        The methods `call_api_method` and `call_column_api_method` can be used to interact with the AG Grid instance on the client.
+        The methods `run_grid_method` and `run_column_method` can be used to interact with the AG Grid instance on the client.
 
         :param options: dictionary of AG Grid options
         :param html_columns: list of columns that should be rendered as HTML (default: `[]`)
@@ -87,7 +87,11 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
         self.run_method('update_grid')
 
     def call_api_method(self, name: str, *args, timeout: float = 1, check_interval: float = 0.01) -> AwaitableResponse:
-        """Call an AG Grid API method.
+        """DEPRECATED: Use `run_grid_method` instead."""
+        return self.run_grid_method(name, *args, timeout=timeout, check_interval=check_interval)
+
+    def run_grid_method(self, name: str, *args, timeout: float = 1, check_interval: float = 0.01) -> AwaitableResponse:
+        """Run an AG Grid API method.
 
         See `AG Grid API <https://www.ag-grid.com/javascript-data-grid/grid-api/>`_ for a list of methods.
 
@@ -101,11 +105,15 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
 
         :return: AwaitableResponse that can be awaited to get the result of the method call
         """
-        return self.run_method('call_api_method', name, *args, timeout=timeout, check_interval=check_interval)
+        return self.run_method('run_grid_method', name, *args, timeout=timeout, check_interval=check_interval)
+
+    def call_column_method(self, name: str, *args, timeout: float = 1, check_interval: float = 0.01) -> AwaitableResponse:
+        """DEPRECATED: Use `run_column_method` instead."""
+        return self.run_column_method(name, *args, timeout=timeout, check_interval=check_interval)
 
-    def call_column_api_method(self, name: str, *args,
-                               timeout: float = 1, check_interval: float = 0.01) -> AwaitableResponse:
-        """Call an AG Grid Column API method.
+    def run_column_method(self, name: str, *args,
+                          timeout: float = 1, check_interval: float = 0.01) -> AwaitableResponse:
+        """Run 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.
 
@@ -119,7 +127,7 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
 
         :return: AwaitableResponse that can be awaited to get the result of the method call
         """
-        return self.run_method('call_column_api_method', name, *args, timeout=timeout, check_interval=check_interval)
+        return self.run_method('run_column_method', name, *args, timeout=timeout, check_interval=check_interval)
 
     async def get_selected_rows(self) -> List[Dict]:
         """Get the currently selected rows.
@@ -130,7 +138,7 @@ class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-comm
 
         :return: list of selected row data
         """
-        result = await self.call_api_method('getSelectedRows')
+        result = await self.run_grid_method('getSelectedRows')
         return cast(List[Dict], result)
 
     async def get_selected_row(self) -> Optional[Dict]:

+ 9 - 0
nicegui/elements/json_editor.js

@@ -31,6 +31,15 @@ export default {
         this.editor.destroy();
       }
     },
+    run_editor_method(name, ...args) {
+      if (this.editor) {
+        if (name.startsWith(":")) {
+          name = name.slice(1);
+          args = args.map((arg) => new Function("return " + arg)());
+        }
+        return this.editor[name](...args);
+      }
+    },
   },
   props: {
     properties: Object,

+ 19 - 0
nicegui/elements/json_editor.py

@@ -1,5 +1,6 @@
 from typing import Callable, Dict, Optional
 
+from ..awaitable_response import AwaitableResponse
 from ..element import Element
 from ..events import GenericEventArguments, JsonEditorChangeEventArguments, JsonEditorSelectEventArguments, handle_event
 
@@ -42,3 +43,21 @@ class JsonEditor(Element, component='json_editor.js', exposed_libraries=['lib/va
     def update(self) -> None:
         super().update()
         self.run_method('update_editor')
+
+    def run_editor_method(self, name: str, *args, timeout: float = 1,
+                          check_interval: float = 0.01) -> AwaitableResponse:
+        """Run a method of the JSONEditor instance.
+
+        See the `JSONEditor README <https://github.com/josdejong/svelte-jsoneditor/>`_ for a list of methods.
+
+        If the function is awaited, the result of the method call is returned.
+        Otherwise, the method is executed without waiting for a response.
+
+        :param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
+        :param args: arguments to pass to the method (Python objects or JavaScript expressions)
+        :param timeout: timeout in seconds (default: 1 second)
+        :param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
+
+        :return: AwaitableResponse that can be awaited to get the result of the method call
+        """
+        return self.run_method('run_editor_method', name, *args, timeout=timeout, check_interval=check_interval)

+ 5 - 5
tests/test_aggrid.py

@@ -90,13 +90,13 @@ def test_dynamic_method(screen: Screen):
     assert 48 <= heights[2] <= 50
 
 
-def test_call_api_method_with_argument(screen: Screen):
+def test_run_grid_method_with_argument(screen: Screen):
     grid = ui.aggrid({
         'columnDefs': [{'field': 'name', 'filter': True}],
         'rowData': [{'name': 'Alice'}, {'name': 'Bob'}, {'name': 'Carol'}],
     })
     filter_model = {'name': {'filterType': 'text', 'type': 'equals', 'filter': 'Alice'}}
-    ui.button('Filter', on_click=lambda: grid.call_api_method('setFilterModel', filter_model))
+    ui.button('Filter', on_click=lambda: grid.run_grid_method('setFilterModel', filter_model))
 
     screen.open('/')
     screen.should_contain('Alice')
@@ -108,12 +108,12 @@ def test_call_api_method_with_argument(screen: Screen):
     screen.should_not_contain('Carol')
 
 
-def test_call_column_api_method_with_argument(screen: Screen):
+def test_run_column_method_with_argument(screen: Screen):
     grid = ui.aggrid({
         'columnDefs': [{'field': 'name'}, {'field': 'age', 'hide': True}],
         'rowData': [{'name': 'Alice', 'age': '18'}, {'name': 'Bob', 'age': '21'}, {'name': 'Carol', 'age': '42'}],
     })
-    ui.button('Show Age', on_click=lambda: grid.call_column_api_method('setColumnVisible', 'age', True))
+    ui.button('Show Age', on_click=lambda: grid.run_column_method('setColumnVisible', 'age', True))
 
     screen.open('/')
     screen.should_contain('Alice')
@@ -188,7 +188,7 @@ def test_create_dynamically(screen: Screen):
 
 def test_api_method_after_creation(screen: Screen):
     options = {'columnDefs': [{'field': 'name'}], 'rowData': [{'name': 'Alice'}]}
-    ui.button('Create', on_click=lambda: ui.aggrid(options).call_api_method('selectAll'))
+    ui.button('Create', on_click=lambda: ui.aggrid(options).run_grid_method('selectAll'))
 
     screen.open('/')
     screen.click('Create')

+ 20 - 0
tests/test_json_editor.py

@@ -0,0 +1,20 @@
+from nicegui import ui
+
+from .screen import Screen
+
+
+def test_json_editor_methods(screen: Screen):
+    editor = ui.json_editor({'content': {'json': {'a': 1, 'b': 2}}})
+
+    async def get_data():
+        data = await editor.run_editor_method('get')
+        ui.label(f'Data: {data}')
+    ui.button('Get Data', on_click=get_data)
+
+    screen.open('/')
+    screen.should_contain('text')
+    screen.should_contain('tree')
+    screen.should_contain('table')
+
+    screen.click('Get Data')
+    screen.should_contain("Data: {'json': {'a': 1, 'b': 2}}")

+ 2 - 2
website/documentation/content/aggrid_documentation.py

@@ -25,8 +25,8 @@ def main_demo() -> None:
         grid.update()
 
     ui.button('Update', on_click=update)
-    ui.button('Select all', on_click=lambda: grid.call_api_method('selectAll'))
-    ui.button('Show parent', on_click=lambda: grid.call_column_api_method('setColumnVisible', 'parent', True))
+    ui.button('Select all', on_click=lambda: grid.run_grid_method('selectAll'))
+    ui.button('Show parent', on_click=lambda: grid.run_column_method('setColumnVisible', 'parent', True))
 
 
 @doc.demo('Select AG Grid Rows', '''

+ 28 - 0
website/documentation/content/json_editor_documentation.py

@@ -23,4 +23,32 @@ def main_demo() -> None:
                    on_change=lambda e: ui.notify(f'Change: {e}'))
 
 
+@doc.demo('Run methods', '''
+    You can run methods of the JSONEditor instance using the `run_editor_method` method.
+    This demo shows how to expand and collapse all nodes and how to get the current data.
+
+    The colon ":" in front of the method name "expand" indicates that the value "path => true" is a JavaScript expression
+    that is evaluated on the client before it is passed to the method.
+''')
+def methods_demo() -> None:
+    json = {
+        'Name': 'Alice',
+        'Age': 42,
+        'Address': {
+            'Street': 'Main Street',
+            'City': 'Wonderland',
+        },
+    }
+    editor = ui.json_editor({'content': {'json': json}})
+
+    ui.button('Expand', on_click=lambda: editor.run_editor_method(':expand', 'path => true'))
+    ui.button('Collapse', on_click=lambda: editor.run_editor_method(':expand', 'path => false'))
+    ui.button('Readonly', on_click=lambda: editor.run_editor_method('updateProps', {'readOnly': True}))
+
+    async def get_data() -> None:
+        data = await editor.run_editor_method('get')
+        ui.notify(data)
+    ui.button('Get Data', on_click=get_data)
+
+
 doc.reference(ui.json_editor)