Sfoglia il codice sorgente

ui.tree select/deselect, tick/untick programmatically (#3476)

* Methods: select & deselect implemented with test.

* Methods: tick (all) & untick (all) implemented with test.

* Demo added to tree details documentation page.

* Update tree.py to correct param description

* code review

* tiny cleanup

---------

Co-authored-by: Falko Schindler <falko@zauberzeug.com>
Adam Horvath 9 mesi fa
parent
commit
1a044b2d86

+ 33 - 1
nicegui/elements/tree.py

@@ -41,7 +41,7 @@ class Tree(Element):
         self._props['node-key'] = node_key
         self._props['node-key'] = node_key
         self._props['label-key'] = label_key
         self._props['label-key'] = label_key
         self._props['children-key'] = children_key
         self._props['children-key'] = children_key
-        self._props['selected'] = []
+        self._props['selected'] = None
         self._props['expanded'] = []
         self._props['expanded'] = []
         self._props['ticked'] = []
         self._props['ticked'] = []
         if tick_strategy is not None:
         if tick_strategy is not None:
@@ -78,6 +78,20 @@ class Tree(Element):
         self._select_handlers.append(callback)
         self._select_handlers.append(callback)
         return self
         return self
 
 
+    def select(self, node_key: Optional[str]) -> Self:
+        """Select the given node.
+
+        :param node_key: node key to select
+        """
+        if self._props['selected'] != node_key:
+            self._props['selected'] = node_key
+            self.update()
+        return self
+
+    def deselect(self) -> Self:
+        """Remove node selection."""
+        return self.select(None)
+
     def on_expand(self, callback: Callable[..., Any]) -> Self:
     def on_expand(self, callback: Callable[..., Any]) -> Self:
         """Add a callback to be invoked when the expansion changes."""
         """Add a callback to be invoked when the expansion changes."""
         self._expand_handlers.append(callback)
         self._expand_handlers.append(callback)
@@ -88,6 +102,24 @@ class Tree(Element):
         self._tick_handlers.append(callback)
         self._tick_handlers.append(callback)
         return self
         return self
 
 
+    def tick(self, node_keys: Optional[List[str]] = None) -> Self:
+        """Tick the given nodes.
+
+        :param node_keys: list of node keys to tick or ``None`` to tick all nodes (default: ``None``)
+        """
+        self._props['ticked'][:] = self._find_node_keys(node_keys).union(self._props['ticked'])
+        self.update()
+        return self
+
+    def untick(self, node_keys: Optional[List[str]] = None) -> Self:
+        """Remove tick from the given nodes.
+
+        :param node_keys: list of node keys to untick or ``None`` to untick all nodes (default: ``None``)
+        """
+        self._props['ticked'][:] = set(self._props['ticked']).difference(self._find_node_keys(node_keys))
+        self.update()
+        return self
+
     def expand(self, node_keys: Optional[List[str]] = None) -> Self:
     def expand(self, node_keys: Optional[List[str]] = None) -> Self:
         """Expand the given nodes.
         """Expand the given nodes.
 
 

+ 46 - 0
tests/test_tree.py

@@ -62,3 +62,49 @@ def test_expand_and_collapse_nodes(screen: Screen):
     screen.should_not_contain('2')
     screen.should_not_contain('2')
     screen.should_contain('A')
     screen.should_contain('A')
     screen.should_contain('B')
     screen.should_contain('B')
+
+
+def test_select_deselect_node(screen: Screen):
+    tree = ui.tree([
+        {'id': 'numbers', 'children': [{'id': '1'}, {'id': '2'}]},
+        {'id': 'letters', 'children': [{'id': 'A'}, {'id': 'B'}]},
+    ], label_key='id')
+
+    ui.button('Select', on_click=lambda: tree.select('2'))
+    ui.button('Deselect', on_click=tree.deselect)
+    ui.label().bind_text_from(tree._props, 'selected', lambda x: f'Selected: {x}')
+
+    screen.open('/')
+    screen.click('Select')
+    screen.should_contain('Selected: 2')
+
+    screen.click('Deselect')
+    screen.should_contain('Selected: None')
+
+
+def test_tick_untick_node_or_nodes(screen: Screen):
+    tree = ui.tree([
+        {'id': 'numbers', 'children': [{'id': '1'}, {'id': '2'}]},
+        {'id': 'letters', 'children': [{'id': 'A'}, {'id': 'B'}]},
+    ], label_key='id', tick_strategy='leaf')
+
+    ui.button('Tick some', on_click=lambda: tree.tick(['1', '2', 'B']))
+    ui.button('Untick some', on_click=lambda: tree.untick(['1', 'B']))
+    ui.button('Tick all', on_click=tree.tick)
+    ui.button('Untick all', on_click=tree.untick)
+    ui.label().bind_text_from(tree._props, 'ticked', lambda x: f'Ticked: {sorted(x)}')
+
+    screen.open('/')
+    screen.should_contain('Ticked: []')
+
+    screen.click('Tick some')
+    screen.should_contain("Ticked: ['1', '2', 'B']")
+
+    screen.click('Untick some')
+    screen.should_contain("Ticked: ['2']")
+
+    screen.click('Tick all')
+    screen.should_contain("Ticked: ['1', '2', 'A', 'B', 'letters', 'numbers']")
+
+    screen.click('Untick all')
+    screen.should_contain('Ticked: []')

+ 40 - 8
website/documentation/content/tree_documentation.py

@@ -35,7 +35,17 @@ def tree_with_custom_header_and_body():
     ''')
     ''')
 
 
 
 
-@doc.demo('Expand and collapse programmatically', '''
+@doc.demo('Tree with checkboxes', '''
+    The tree can be used with checkboxes by setting the "tick-strategy" prop.
+''')
+def tree_with_checkboxes():
+    ui.tree([
+        {'id': 'A', 'children': [{'id': 'A1'}, {'id': 'A2'}]},
+        {'id': 'B', 'children': [{'id': 'B1'}, {'id': 'B2'}]},
+    ], label_key='id', tick_strategy='leaf', on_tick=lambda e: ui.notify(e.value))
+
+
+@doc.demo('Expand/collapse programmatically', '''
     The whole tree or individual nodes can be toggled programmatically using the `expand()` and `collapse()` methods.
     The whole tree or individual nodes can be toggled programmatically using the `expand()` and `collapse()` methods.
     This even works if a node is disabled (e.g. not clickable by the user).
     This even works if a node is disabled (e.g. not clickable by the user).
 ''')
 ''')
@@ -52,14 +62,36 @@ def expand_programmatically():
         ui.button('- A', on_click=lambda: t.collapse(['A']))
         ui.button('- A', on_click=lambda: t.collapse(['A']))
 
 
 
 
-@doc.demo('Tree with checkboxes', '''
-    The tree can be used with checkboxes by setting the "tick-strategy" prop.
+@doc.demo('Select/deselect programmatically', '''
+    You can select or deselect nodes with the `select()` and `deselect()` methods.
 ''')
 ''')
-def tree_with_checkboxes():
-    ui.tree([
-        {'id': 'A', 'children': [{'id': 'A1'}, {'id': 'A2'}]},
-        {'id': 'B', 'children': [{'id': 'B1'}, {'id': 'B2'}]},
-    ], label_key='id', tick_strategy='leaf', on_tick=lambda e: ui.notify(e.value))
+def select_programmatically():
+    t = ui.tree([
+        {'id': 'numbers', 'children': [{'id': '1'}, {'id': '2'}]},
+        {'id': 'letters', 'children': [{'id': 'A'}, {'id': 'B'}]},
+    ], label_key='id').expand()
+
+    with ui.row():
+        ui.button('Select A', on_click=lambda: t.select('A'))
+        ui.button('Deselect A', on_click=t.deselect)
+
+
+@doc.demo('Tick/untick programmatically', '''
+    After setting a `tick_strategy`, you can tick or untick nodes with the `tick()` and `untick()` methods.
+    You can either specify a list of node keys or `None` to tick or untick all nodes.
+''')
+def tick_programmatically():
+    t = ui.tree([
+        {'id': 'numbers', 'children': [{'id': '1'}, {'id': '2'}]},
+        {'id': 'letters', 'children': [{'id': 'A'}, {'id': 'B'}]},
+    ], label_key='id', tick_strategy='leaf').expand()
+
+    with ui.row():
+        ui.button('Tick 1, 2 and B', on_click=lambda: t.tick(['1', '2', 'B']))
+        ui.button('Untick 2 and B', on_click=lambda: t.untick(['2', 'B']))
+    with ui.row():
+        ui.button('Tick all', on_click=t.tick)
+        ui.button('Untick all', on_click=t.untick)
 
 
 
 
 doc.reference(ui.tree)
 doc.reference(ui.tree)