-
Notifications
You must be signed in to change notification settings - Fork 193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
data table tutorial #304
data table tutorial #304
Changes from all commits
3267c67
293efb7
4bac50c
c9dc353
6d2def0
655d324
20adda0
a1c4a99
f474f30
b2b7fc6
f16d4ee
4661530
8e05206
8376deb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
```python exec | ||
import reflex as rx | ||
from pcweb.base_state import State | ||
from pcweb.templates.docpage import docdemobox | ||
from pcweb.pages.docs.datatable_tutorial.states import DataTableState, DataTableState2 | ||
|
||
``` | ||
|
||
|
||
|
||
|
||
|
||
# Adding Interactivity to our DataTable | ||
|
||
|
||
Now we will add interactivity to our datatable. We do this using event handlers and event triggers. | ||
|
||
|
||
The first example implements a handler for the `on_cell_clicked` event trigger, which is called when the user clicks on a cell of the data editor. The event trigger receives the coordinates of the cell. | ||
|
||
```python | ||
class DataTableState(rx.State): | ||
clicked_cell: str = "Cell clicked: " | ||
|
||
... | ||
|
||
|
||
def get_clicked_data(self, pos: tuple[int, int]) -> str: | ||
self.clicked_cell = f"Cell clicked: \{pos}" | ||
|
||
``` | ||
|
||
The state has a var called `clicked_cell` that will store a message about which cell was clicked. We define an event handler `get_clicked_data` that updates the value of the `clicked_cell` var when it is called. In essence, we have clicked on a cell, called the `on_cell_clicked` event trigger which calls the `get_clicked_data` event handler, which updates the `clicked_cell` var. | ||
|
||
|
||
```python demo | ||
rx.text(DataTableState.clicked_cell, as_="strong") | ||
``` | ||
|
||
```python demo | ||
rx.data_editor( | ||
columns=DataTableState.cols, | ||
data=DataTableState.data, | ||
on_cell_clicked=DataTableState.get_clicked_data, | ||
) | ||
``` | ||
|
||
|
||
The event handler `on_cell_context_menu` can be used in the same way as `on_cell_clicked`, except here the event trigger is called when the user right clicks, i.e. when the cell should show a context menu. | ||
|
||
## Editing cells | ||
|
||
Another important type of interactivity we will showcase is how to edit cells. Here we use the `on_cell_edited` event trigger to update the data based on what the user entered. | ||
|
||
```python | ||
class DataTableState(rx.State): | ||
clicked_cell: str = "Cell clicked: " | ||
edited_cell: str = "Cell edited: " | ||
|
||
... | ||
|
||
|
||
def get_clicked_data(self, pos) -> str: | ||
self.clicked_cell = f"Cell clicked: \{pos}" | ||
|
||
def get_edited_data(self, pos, val) -> str: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest more type annotations here. What is the type of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. val is a dictionary like this: {'kind': 'text', 'data': '20', 'displayData': 'Hermione Jean Granger', 'readonly': False, 'allowOverlay': True} and we index in and just use the 'data' key for now |
||
col, row = pos | ||
self.data[row][col] = val["data"] | ||
self.edited_cell = f"Cell edited: \{pos}, Cell value: \{val["data"]}" | ||
|
||
``` | ||
|
||
The `on_cell_edited` event trigger is called when the user modifies the content of a cell. It receives the coordinates of the cell and the modified content. We pass these into the `get_edited_data` event handler and use them to update the `data` state var at the appropriate position. We then update the `edited_cell` var value. | ||
|
||
|
||
```python demo | ||
rx.text(DataTableState.edited_cell, as_="strong") | ||
``` | ||
|
||
```python demo | ||
rx.data_editor( | ||
columns=DataTableState.cols, | ||
data=DataTableState.data, | ||
on_cell_clicked=DataTableState.get_clicked_data, | ||
on_cell_edited=DataTableState.get_edited_data, | ||
) | ||
``` | ||
|
||
## Group Header | ||
|
||
We can define group headers which are headers that encompass a group of columns. We define these in the `columns` using the `group` property such as `"group": "Data"`. The `columns` would now be defined as below. Only the `Title` does not fall under a group header, all the rest fall under the `Data` group header. | ||
|
||
```python | ||
class DataTableState2(rx.State): | ||
"""The app state.""" | ||
|
||
... | ||
|
||
cols: list[dict] = [ | ||
{\"title": "Title", "type": "str"}, | ||
{ | ||
"title": "Name", | ||
"type": "str", | ||
"group": "Data", | ||
"width": 300, | ||
}, | ||
{ | ||
"title": "Birth", | ||
"type": "str", | ||
"group": "Data", | ||
"width": 150, | ||
}, | ||
{ | ||
"title": "Human", | ||
"type": "bool", | ||
"group": "Data", | ||
"width": 80, | ||
}, | ||
{ | ||
"title": "House", | ||
"type": "str", | ||
"group": "Data", | ||
}, | ||
{ | ||
"title": "Wand", | ||
"type": "str", | ||
"group": "Data", | ||
"width": 250, | ||
}, | ||
{ | ||
"title": "Patronus", | ||
"type": "str", | ||
"group": "Data", | ||
}, | ||
{ | ||
"title": "Blood status", | ||
"type": "str", | ||
"group": "Data", | ||
"width": 200, | ||
}, | ||
] | ||
|
||
... | ||
``` | ||
|
||
|
||
The table now has a header as below. | ||
|
||
```python demo | ||
rx.data_editor( | ||
columns=DataTableState2.cols, | ||
data=DataTableState2.data, | ||
on_cell_clicked=DataTableState2.get_clicked_data, | ||
on_cell_edited=DataTableState2.get_edited_data, | ||
) | ||
``` | ||
|
||
There are several event triggers we can apply to the group header. | ||
|
||
|
||
```python | ||
class DataTableState2(rx.State): | ||
"""The app state.""" | ||
|
||
right_clicked_group_header : str = "Group header right clicked: " | ||
|
||
... | ||
|
||
def get_group_header_right_click(self, index, val): | ||
self.right_clicked_group_header = f"Group header right clicked at index: \{index}, Group header value: \{val['group']}" | ||
Comment on lines
+169
to
+170
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure i see the utility in this example. it works, but i don't know why someone would want this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is just an example that shows how to do a right click event handler. I assume people will figure out their own uses for it if they want to ever create events based on right clicks from the user |
||
|
||
``` | ||
|
||
```python demo | ||
rx.text(DataTableState2.right_clicked_group_header, as_="strong") | ||
``` | ||
|
||
```python demo | ||
rx.data_editor( | ||
columns=DataTableState2.cols, | ||
data=DataTableState2.data, | ||
on_cell_clicked=DataTableState2.get_clicked_data, | ||
on_cell_edited=DataTableState2.get_edited_data, | ||
on_group_header_context_menu=DataTableState2.get_group_header_right_click, | ||
) | ||
``` | ||
|
||
In this example we use the `on_group_header_context_menu` event trigger which is called when the user right-clicks on a group header. It returns the `index` and the `data` of the group header. We can also use the `on_group_header_clicked` and `on_group_header_renamed` event triggers which are called when the user left-clicks on a group header and when a user renames a group header respectively. | ||
|
||
|
||
## More Event Triggers | ||
|
||
There are several other event triggers that are worth exploring. The `on_item_hovered` event trigger is called whenever the user hovers over an item in the datatable. The `on_delete` event trigger is called when the user deletes a cell from the datatable. | ||
tgberkeley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
The final event trigger to check out is `on_column_resize`. `on_column_resize` allows us to respond to the user dragging the handle between columns. The event trigger returns the `col` we are adjusting and the new `width` we have defined. The `col` that is returned is a dictionary for example: `\{'title': 'Name', 'type': 'str', 'group': 'Data', 'width': 198, 'pos': 1}`. We then index into `self.cols` defined in our state and change the `width` of that column using this code: `self.cols[col['pos']]['width'] = width`. | ||
|
||
|
||
```python | ||
class DataTableState2(rx.State): | ||
"""The app state.""" | ||
|
||
... | ||
|
||
item_hovered: str = "Item Hovered: " | ||
deleted: str = "Deleted: " | ||
|
||
... | ||
|
||
|
||
def get_item_hovered(self, pos) -> str: | ||
self.item_hovered = f"Item Hovered type: \{pos['kind']}, Location: \{pos['location']}" | ||
|
||
def get_deleted_item(self, selection): | ||
self.deleted = f"Deleted cell: \{selection['current']['cell']}" | ||
|
||
def column_resize(self, col, width): | ||
self.cols[col['pos']]['width'] = width | ||
``` | ||
|
||
|
||
```python demo | ||
rx.text(DataTableState2.item_hovered, as_="strong") | ||
``` | ||
|
||
```python demo | ||
rx.text(DataTableState2.deleted, as_="strong") | ||
``` | ||
|
||
```python demo | ||
rx.data_editor( | ||
columns=DataTableState2.cols, | ||
data=DataTableState2.data, | ||
on_cell_clicked=DataTableState2.get_clicked_data, | ||
on_cell_edited=DataTableState2.get_edited_data, | ||
on_group_header_context_menu=DataTableState2.get_group_header_right_click, | ||
on_item_hovered=DataTableState2.get_item_hovered, | ||
on_delete=DataTableState2.get_deleted_item, | ||
on_column_resize=DataTableState2.column_resize, | ||
) | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
```python exec | ||
import reflex as rx | ||
from pcweb.pages.docs.datatable_tutorial.states import DataTableState2 | ||
``` | ||
|
||
|
||
|
||
# DataTable Styling | ||
|
||
|
||
There are props that we can explore to ensure the datatable is shaped correctly and reacts in the way we expect. We can set `on_paste` to `True`, which allows us to paste directly into a cell. We can use `draw_focus_ring` to draw a ring around the cells when selected, this defaults to `True` so can be turned off if we do not want it. The `rows` prop can be used to hard code the number of rows that we show. | ||
|
||
`freeze_columns` is used to keep a certain number of the left hand columns frozen when scrolling horizontally. `group_header_height` and `header_height` define the height of the group header and the individual headers respectively. `max_column_width` and `min_column_width` define how large or small the columns are allowed to be with the manual column resizing. We can also define the `row_height` to make the rows more nicely spaced. | ||
|
||
We can add `row_markers`, which appear on the furthest left side of the table. They can take values of `'none', 'number', 'checkbox', 'both', 'clickable-number'`. We can set `smooth_scroll_x` and `smooth_scroll_y`, which allows us to smoothly scroll along the columns and rows. | ||
|
||
By default there is a `vertical_border` between the columns, we can turn it off by setting this prop to `False`. We can define how many columns a user can select at a time by setting the `column_select` prop. It can take values of `"none", "single", "multi"`. | ||
|
||
We can allow `overscroll_x`, which allows users to scroll past the limit of the actual horizontal content. There is an equivalent `overscroll_y`. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest directly linking to the component docs where all of these props (and more) are displayed in table form. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added |
||
Check out [these docs](https://reflex.dev/docs/library/datadisplay/dataeditor/) for more information on these props. | ||
|
||
```python demo | ||
rx.data_editor( | ||
columns=DataTableState2.cols, | ||
data=DataTableState2.data, | ||
|
||
#rows=4, | ||
on_paste=True, | ||
draw_focus_ring=False, | ||
freeze_columns=2, | ||
group_header_height=50, | ||
header_height=60, | ||
max_column_width=300, | ||
min_column_width=100, | ||
row_height=50, | ||
row_markers='clickable-number', | ||
smooth_scroll_x=True, | ||
vertical_border=False, | ||
column_select="multi", | ||
overscroll_x=100, | ||
|
||
|
||
on_cell_clicked=DataTableState2.get_clicked_data, | ||
on_cell_edited=DataTableState2.get_edited_data, | ||
on_group_header_context_menu=DataTableState2.get_group_header_right_click, | ||
on_item_hovered=DataTableState2.get_item_hovered, | ||
on_delete=DataTableState2.get_deleted_item, | ||
on_column_resize=DataTableState2.column_resize, | ||
) | ||
``` | ||
|
||
|
||
## Theming | ||
|
||
Lastly there is a `theme` prop that allows us to pass in all color and font information for the data table. | ||
|
||
```python | ||
darkTheme = { | ||
"accentColor": "#8c96ff", | ||
"accentLight": "rgba(202, 206, 255, 0.253)", | ||
"textDark": "#ffffff", | ||
"textMedium": "#b8b8b8", | ||
"textLight": "#a0a0a0", | ||
"textBubble": "#ffffff", | ||
"bgIconHeader": "#b8b8b8", | ||
"fgIconHeader": "#000000", | ||
"textHeader": "#a1a1a1", | ||
"textHeaderSelected": "#000000", | ||
"bgCell": "#16161b", | ||
"bgCellMedium": "#202027", | ||
"bgHeader": "#212121", | ||
"bgHeaderHasFocus": "#474747", | ||
"bgHeaderHovered": "#404040", | ||
"bgBubble": "#212121", | ||
"bgBubbleSelected": "#000000", | ||
"bgSearchResult": "#423c24", | ||
"borderColor": "rgba(225,225,225,0.2)", | ||
"drilldownBorder": "rgba(225,225,225,0.4)", | ||
"linkColor": "#4F5DFF", | ||
"headerFontStyle": "bold 14px", | ||
"baseFontStyle": "13px", | ||
"fontFamily": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif", | ||
} | ||
``` | ||
|
||
```python exec | ||
darkTheme = { | ||
"accentColor": "#8c96ff", | ||
"accentLight": "rgba(202, 206, 255, 0.253)", | ||
"textDark": "#ffffff", | ||
"textMedium": "#b8b8b8", | ||
"textLight": "#a0a0a0", | ||
"textBubble": "#ffffff", | ||
"bgIconHeader": "#b8b8b8", | ||
"fgIconHeader": "#000000", | ||
"textHeader": "#a1a1a1", | ||
"textHeaderSelected": "#000000", | ||
"bgCell": "#16161b", | ||
"bgCellMedium": "#202027", | ||
"bgHeader": "#212121", | ||
"bgHeaderHasFocus": "#474747", | ||
"bgHeaderHovered": "#404040", | ||
"bgBubble": "#212121", | ||
"bgBubbleSelected": "#000000", | ||
"bgSearchResult": "#423c24", | ||
"borderColor": "rgba(225,225,225,0.2)", | ||
"drilldownBorder": "rgba(225,225,225,0.4)", | ||
"linkColor": "#4F5DFF", | ||
"headerFontStyle": "bold 14px", | ||
"baseFontStyle": "13px", | ||
"fontFamily": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif", | ||
} | ||
``` | ||
|
||
```python demo | ||
rx.data_editor( | ||
columns=DataTableState2.cols, | ||
data=DataTableState2.data, | ||
|
||
on_paste=True, | ||
draw_focus_ring=False, | ||
freeze_columns=2, | ||
group_header_height=50, | ||
header_height=60, | ||
max_column_width=300, | ||
min_column_width=100, | ||
row_height=50, | ||
row_markers='clickable-number', | ||
smooth_scroll_x=True, | ||
vertical_border=False, | ||
column_select="multi", | ||
overscroll_x=100, | ||
theme=darkTheme, | ||
|
||
|
||
on_cell_clicked=DataTableState2.get_clicked_data, | ||
on_cell_edited=DataTableState2.get_edited_data, | ||
on_group_header_context_menu=DataTableState2.get_group_header_right_click, | ||
on_item_hovered=DataTableState2.get_item_hovered, | ||
on_delete=DataTableState2.get_deleted_item, | ||
on_column_resize=DataTableState2.column_resize, | ||
) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have any example anywhere of displaying a custom context menu? I feel like this is a bit of a tease if we're not going to show the code. And it's a bit more complicated than it sounds here.
At the end of the day, we might leave this line out, or save it for later in the tutorial/API reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We give an example of on_group_header_context_menu later on. These are not really custom context menus, it is just an event handler that picks up a right mouse click