tree.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. from typing import Any, Dict, Iterator, List, Literal, Optional, Set
  2. from typing_extensions import Self
  3. from ..events import GenericEventArguments, Handler, ValueChangeEventArguments, handle_event
  4. from .mixins.filter_element import FilterElement
  5. class Tree(FilterElement):
  6. def __init__(self,
  7. nodes: List[Dict], *,
  8. node_key: str = 'id',
  9. label_key: str = 'label',
  10. children_key: str = 'children',
  11. on_select: Optional[Handler[ValueChangeEventArguments]] = None,
  12. on_expand: Optional[Handler[ValueChangeEventArguments]] = None,
  13. on_tick: Optional[Handler[ValueChangeEventArguments]] = None,
  14. tick_strategy: Optional[Literal['leaf', 'leaf-filtered', 'strict']] = None,
  15. ) -> None:
  16. """Tree
  17. Display hierarchical data using Quasar's `QTree <https://quasar.dev/vue-components/tree>`_ component.
  18. If using IDs, make sure they are unique within the whole tree.
  19. To use checkboxes and ``on_tick``, set the ``tick_strategy`` parameter to "leaf", "leaf-filtered" or "strict".
  20. :param nodes: hierarchical list of node objects
  21. :param node_key: property name of each node object that holds its unique id (default: "id")
  22. :param label_key: property name of each node object that holds its label (default: "label")
  23. :param children_key: property name of each node object that holds its list of children (default: "children")
  24. :param on_select: callback which is invoked when the node selection changes
  25. :param on_expand: callback which is invoked when the node expansion changes
  26. :param on_tick: callback which is invoked when a node is ticked or unticked
  27. :param tick_strategy: whether and how to use checkboxes ("leaf", "leaf-filtered" or "strict"; default: ``None``)
  28. """
  29. super().__init__(tag='q-tree', filter=None)
  30. self._props['nodes'] = nodes
  31. self._props['node-key'] = node_key
  32. self._props['label-key'] = label_key
  33. self._props['children-key'] = children_key
  34. if on_select:
  35. self._props['selected'] = None
  36. if on_expand:
  37. self._props['expanded'] = []
  38. if on_tick or tick_strategy:
  39. self._props['ticked'] = []
  40. self._props['tick-strategy'] = tick_strategy or 'leaf'
  41. self._select_handlers = [on_select] if on_select else []
  42. self._expand_handlers = [on_expand] if on_expand else []
  43. self._tick_handlers = [on_tick] if on_tick else []
  44. # https://github.com/zauberzeug/nicegui/issues/1385
  45. self._props.add_warning('default-expand-all',
  46. 'The prop "default-expand-all" is not supported by `ui.tree`. '
  47. 'Use ".expand()" instead.')
  48. def update_prop(name: str, value: Any) -> None:
  49. if self._props[name] != value:
  50. self._props[name] = value
  51. self.update()
  52. def handle_selected(e: GenericEventArguments) -> None:
  53. previous_value = self._props.get('selected')
  54. update_prop('selected', e.args)
  55. args = ValueChangeEventArguments(sender=self, client=self.client,
  56. value=e.args, previous_value=previous_value)
  57. for handler in self._select_handlers:
  58. handle_event(handler, args)
  59. self.on('update:selected', handle_selected)
  60. def handle_expanded(e: GenericEventArguments) -> None:
  61. previous_value = self._props.get('expanded')
  62. update_prop('expanded', e.args)
  63. args = ValueChangeEventArguments(sender=self, client=self.client,
  64. value=e.args, previous_value=previous_value)
  65. for handler in self._expand_handlers:
  66. handle_event(handler, args)
  67. self.on('update:expanded', handle_expanded)
  68. def handle_ticked(e: GenericEventArguments) -> None:
  69. previous_value = self._props.get('ticked')
  70. update_prop('ticked', e.args)
  71. args = ValueChangeEventArguments(sender=self, client=self.client,
  72. value=e.args, previous_value=previous_value)
  73. for handler in self._tick_handlers:
  74. handle_event(handler, args)
  75. self.on('update:ticked', handle_ticked)
  76. def on_select(self, callback: Handler[ValueChangeEventArguments]) -> Self:
  77. """Add a callback to be invoked when the selection changes."""
  78. self._props.setdefault('selected', None)
  79. self._select_handlers.append(callback)
  80. return self
  81. def select(self, node_key: Optional[str]) -> Self:
  82. """Select the given node.
  83. :param node_key: node key to select
  84. """
  85. self._props.setdefault('selected', None)
  86. if self._props['selected'] != node_key:
  87. self._props['selected'] = node_key
  88. self.update()
  89. return self
  90. def deselect(self) -> Self:
  91. """Remove node selection."""
  92. return self.select(None)
  93. def on_expand(self, callback: Handler[ValueChangeEventArguments]) -> Self:
  94. """Add a callback to be invoked when the expansion changes."""
  95. self._props.setdefault('expanded', [])
  96. self._expand_handlers.append(callback)
  97. return self
  98. def on_tick(self, callback: Handler[ValueChangeEventArguments]) -> Self:
  99. """Add a callback to be invoked when a node is ticked or unticked."""
  100. self._props.setdefault('ticked', [])
  101. self._props.setdefault('tick-strategy', 'leaf')
  102. self._tick_handlers.append(callback)
  103. return self
  104. def tick(self, node_keys: Optional[List[str]] = None) -> Self:
  105. """Tick the given nodes.
  106. :param node_keys: list of node keys to tick or ``None`` to tick all nodes (default: ``None``)
  107. """
  108. self._props.setdefault('ticked', [])
  109. self._props['ticked'][:] = self._find_node_keys(node_keys).union(self._props['ticked'])
  110. self.update()
  111. return self
  112. def untick(self, node_keys: Optional[List[str]] = None) -> Self:
  113. """Remove tick from the given nodes.
  114. :param node_keys: list of node keys to untick or ``None`` to untick all nodes (default: ``None``)
  115. """
  116. self._props.setdefault('ticked', [])
  117. self._props['ticked'][:] = set(self._props['ticked']).difference(self._find_node_keys(node_keys))
  118. self.update()
  119. return self
  120. def expand(self, node_keys: Optional[List[str]] = None) -> Self:
  121. """Expand the given nodes.
  122. :param node_keys: list of node keys to expand (default: all nodes)
  123. """
  124. self._props.setdefault('expanded', [])
  125. self._props['expanded'][:] = self._find_node_keys(node_keys).union(self._props['expanded'])
  126. self.update()
  127. return self
  128. def collapse(self, node_keys: Optional[List[str]] = None) -> Self:
  129. """Collapse the given nodes.
  130. :param node_keys: list of node keys to collapse (default: all nodes)
  131. """
  132. self._props.setdefault('expanded', [])
  133. self._props['expanded'][:] = set(self._props['expanded']).difference(self._find_node_keys(node_keys))
  134. self.update()
  135. return self
  136. def _find_node_keys(self, node_keys: Optional[List[str]] = None) -> Set[str]:
  137. if node_keys is not None:
  138. return set(node_keys)
  139. CHILDREN_KEY = self._props['children-key']
  140. NODE_KEY = self._props['node-key']
  141. def iterate_nodes(nodes: List[Dict]) -> Iterator[Dict]:
  142. for node in nodes:
  143. yield node
  144. yield from iterate_nodes(node.get(CHILDREN_KEY, []))
  145. return {node[NODE_KEY] for node in iterate_nodes(self._props['nodes'])}