aggrid.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. from typing import Dict, List, Optional, cast
  2. from typing_extensions import Self
  3. from .. import optional_features
  4. from ..awaitable_response import AwaitableResponse
  5. from ..element import Element
  6. try:
  7. import pandas as pd
  8. optional_features.register('pandas')
  9. except ImportError:
  10. pass
  11. class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-community.min.js']):
  12. def __init__(self,
  13. options: Dict, *,
  14. html_columns: List[int] = [], # noqa: B006
  15. theme: str = 'balham',
  16. auto_size_columns: bool = True,
  17. ) -> None:
  18. """AG Grid
  19. An element to create a grid using `AG Grid <https://www.ag-grid.com/>`_.
  20. The methods `run_grid_method` and `run_column_method` can be used to interact with the AG Grid instance on the client.
  21. :param options: dictionary of AG Grid options
  22. :param html_columns: list of columns that should be rendered as HTML (default: `[]`)
  23. :param theme: AG Grid theme (default: 'balham')
  24. :param auto_size_columns: whether to automatically resize columns to fit the grid width (default: `True`)
  25. """
  26. super().__init__()
  27. self._props['options'] = options
  28. self._props['html_columns'] = html_columns[:]
  29. self._props['auto_size_columns'] = auto_size_columns
  30. self._classes.append('nicegui-aggrid')
  31. self._classes.append(f'ag-theme-{theme}')
  32. @classmethod
  33. def from_pandas(cls,
  34. df: 'pd.DataFrame', *,
  35. theme: str = 'balham',
  36. auto_size_columns: bool = True,
  37. options: Dict = {}) -> Self: # noqa: B006
  38. """Create an AG Grid from a Pandas DataFrame.
  39. Note:
  40. If the DataFrame contains non-serializable columns of type `datetime64[ns]`, `timedelta64[ns]`, `complex128` or `period[M]`,
  41. they will be converted to strings.
  42. To use a different conversion, convert the DataFrame manually before passing it to this method.
  43. See `issue 1698 <https://github.com/zauberzeug/nicegui/issues/1698>`_ for more information.
  44. :param df: Pandas DataFrame
  45. :param theme: AG Grid theme (default: 'balham')
  46. :param auto_size_columns: whether to automatically resize columns to fit the grid width (default: `True`)
  47. :param options: dictionary of additional AG Grid options
  48. :return: AG Grid element
  49. """
  50. def is_special_dtype(dtype):
  51. return (pd.api.types.is_datetime64_any_dtype(dtype) or
  52. pd.api.types.is_timedelta64_dtype(dtype) or
  53. pd.api.types.is_complex_dtype(dtype) or
  54. isinstance(dtype, pd.PeriodDtype))
  55. special_cols = df.columns[df.dtypes.apply(is_special_dtype)]
  56. if not special_cols.empty:
  57. df = df.copy()
  58. df[special_cols] = df[special_cols].astype(str)
  59. if isinstance(df.columns, pd.MultiIndex):
  60. raise ValueError('MultiIndex columns are not supported. '
  61. 'You can convert them to strings using something like '
  62. '`df.columns = ["_".join(col) for col in df.columns.values]`.')
  63. return cls({
  64. 'columnDefs': [{'field': str(col)} for col in df.columns],
  65. 'rowData': df.to_dict('records'),
  66. 'suppressDotNotation': True,
  67. **options,
  68. }, theme=theme, auto_size_columns=auto_size_columns)
  69. @property
  70. def options(self) -> Dict:
  71. """The options dictionary."""
  72. return self._props['options']
  73. def update(self) -> None:
  74. super().update()
  75. self.run_method('update_grid')
  76. def call_api_method(self, name: str, *args, timeout: float = 1, check_interval: float = 0.01) -> AwaitableResponse:
  77. """DEPRECATED: Use `run_grid_method` instead."""
  78. return self.run_grid_method(name, *args, timeout=timeout, check_interval=check_interval)
  79. def run_grid_method(self, name: str, *args, timeout: float = 1, check_interval: float = 0.01) -> AwaitableResponse:
  80. """Run an AG Grid API method.
  81. See `AG Grid API <https://www.ag-grid.com/javascript-data-grid/grid-api/>`_ for a list of methods.
  82. If the function is awaited, the result of the method call is returned.
  83. Otherwise, the method is executed without waiting for a response.
  84. :param name: name of the method
  85. :param args: arguments to pass to the method
  86. :param timeout: timeout in seconds (default: 1 second)
  87. :return: AwaitableResponse that can be awaited to get the result of the method call
  88. """
  89. return self.run_method('run_grid_method', name, *args, timeout=timeout, check_interval=check_interval)
  90. def call_column_method(self, name: str, *args, timeout: float = 1, check_interval: float = 0.01) -> AwaitableResponse:
  91. """DEPRECATED: Use `run_column_method` instead."""
  92. return self.run_column_method(name, *args, timeout=timeout, check_interval=check_interval)
  93. def run_column_method(self, name: str, *args,
  94. timeout: float = 1, check_interval: float = 0.01) -> AwaitableResponse:
  95. """Run an AG Grid Column API method.
  96. See `AG Grid Column API <https://www.ag-grid.com/javascript-data-grid/column-api/>`_ for a list of methods.
  97. If the function is awaited, the result of the method call is returned.
  98. Otherwise, the method is executed without waiting for a response.
  99. :param name: name of the method
  100. :param args: arguments to pass to the method
  101. :param timeout: timeout in seconds (default: 1 second)
  102. :return: AwaitableResponse that can be awaited to get the result of the method call
  103. """
  104. return self.run_method('run_column_method', name, *args, timeout=timeout, check_interval=check_interval)
  105. def run_row_method(self, row_id: str, name: str, *args,
  106. timeout: float = 1, check_interval: float = 0.01) -> AwaitableResponse:
  107. """Run an AG Grid API method on a specific row.
  108. See `AG Grid Row Reference <https://www.ag-grid.com/javascript-data-grid/row-object/>`_ for a list of methods.
  109. If the function is awaited, the result of the method call is returned.
  110. Otherwise, the method is executed without waiting for a response.
  111. :param row_id: id of the row (as defined by the ``getRowId`` option)
  112. :param name: name of the method
  113. :param args: arguments to pass to the method
  114. :param timeout: timeout in seconds (default: 1 second)
  115. :return: AwaitableResponse that can be awaited to get the result of the method call
  116. """
  117. return self.run_method('run_row_method', row_id, name, *args, timeout=timeout, check_interval=check_interval)
  118. async def get_selected_rows(self) -> List[Dict]:
  119. """Get the currently selected rows.
  120. This method is especially useful when the grid is configured with ``rowSelection: 'multiple'``.
  121. See `AG Grid API <https://www.ag-grid.com/javascript-data-grid/row-selection/#reference-selection-getSelectedRows>`_ for more information.
  122. :return: list of selected row data
  123. """
  124. result = await self.run_grid_method('getSelectedRows')
  125. return cast(List[Dict], result)
  126. async def get_selected_row(self) -> Optional[Dict]:
  127. """Get the single currently selected row.
  128. This method is especially useful when the grid is configured with ``rowSelection: 'single'``.
  129. :return: row data of the first selection if any row is selected, otherwise `None`
  130. """
  131. rows = await self.get_selected_rows()
  132. return rows[0] if rows else None
  133. async def get_client_data(self, *, timeout: float = 1, check_interval: float = 0.01) -> List[Dict]:
  134. """Get the data from the client including any edits made by the client.
  135. This method is especially useful when the grid is configured with ``'editable': True``.
  136. See `AG Grid API <https://www.ag-grid.com/javascript-data-grid/accessing-data/>`_ for more information.
  137. Note that when editing a cell, the row data is not updated until the cell exits the edit mode.
  138. This does not happen when the cell loses focus, unless ``stopEditingWhenCellsLoseFocus: True`` is set.
  139. :param timeout: timeout in seconds (default: 1 second)
  140. :return: list of row data
  141. """
  142. result = await self.client.run_javascript(f'''
  143. const rowData = [];
  144. getElement({self.id}).gridOptions.api.forEachNode(node => rowData.push(node.data));
  145. return rowData;
  146. ''', timeout=timeout, check_interval=check_interval)
  147. return cast(List[Dict], result)
  148. async def load_client_data(self) -> None:
  149. """Obtain client data and update the element's row data with it.
  150. This syncs edits made by the client in editable cells to the server.
  151. Note that when editing a cell, the row data is not updated until the cell exits the edit mode.
  152. This does not happen when the cell loses focus, unless ``stopEditingWhenCellsLoseFocus: True`` is set.
  153. """
  154. client_row_data = await self.get_client_data()
  155. self.options['rowData'] = client_row_data
  156. self.update()