-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix(data_table): resolve blank cells for non-camelCase column keys #6111
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
| @@ -1,122 +1,3 @@ | ||
| { | ||
| "reflex/__init__.pyi": "0a3ae880e256b9fd3b960e12a2cb51a7", | ||
| "reflex/components/__init__.pyi": "ac05995852baa81062ba3d18fbc489fb", | ||
| "reflex/components/base/__init__.pyi": "16e47bf19e0d62835a605baa3d039c5a", | ||
| "reflex/components/base/app_wrap.pyi": "22e94feaa9fe675bcae51c412f5b67f1", | ||
| "reflex/components/base/body.pyi": "e8ab029a730824bab6d4211203609e6a", | ||
| "reflex/components/base/document.pyi": "311c53c90a60587a82e760103758a3cf", | ||
| "reflex/components/base/error_boundary.pyi": "a678cceea014cb16048647257cd24ba6", | ||
| "reflex/components/base/fragment.pyi": "745f1be02c23a0b25d7c52d7423ec76a", | ||
| "reflex/components/base/link.pyi": "0bc1d26ee29d8864aed14a12991bd47d", | ||
| "reflex/components/base/meta.pyi": "129aecf65ab53f756c4d1cbe1d0b188d", | ||
| "reflex/components/base/script.pyi": "e5f506d1d0d6712cb9e597a781eb3941", | ||
| "reflex/components/base/strict_mode.pyi": "6b72e16caadf7158ab744a0ab751b010", | ||
| "reflex/components/core/__init__.pyi": "007170b97e58bdf28b2aee381d91c0c7", | ||
| "reflex/components/core/auto_scroll.pyi": "18068d22aca7244a08cd0c5a897c0950", | ||
| "reflex/components/core/banner.pyi": "fd93e7a92961de8524718ad32135c37c", | ||
| "reflex/components/core/clipboard.pyi": "a844eb927d9bc2a43f5e88161b258539", | ||
| "reflex/components/core/debounce.pyi": "055da7aa890f44fb4d48bd5978f1a874", | ||
| "reflex/components/core/helmet.pyi": "43f8497c8fafe51e29dca1dd535d143a", | ||
| "reflex/components/core/html.pyi": "86eb9d4c1bb4807547b2950d9a32e9fd", | ||
| "reflex/components/core/sticky.pyi": "cb763b986a9b0654d1a3f33440dfcf60", | ||
| "reflex/components/core/upload.pyi": "6dc28804a6dddf903e31162e87c1b023", | ||
| "reflex/components/core/window_events.pyi": "af33ccec866b9540ee7fbec6dbfbd151", | ||
| "reflex/components/datadisplay/__init__.pyi": "52755871369acbfd3a96b46b9a11d32e", | ||
| "reflex/components/datadisplay/code.pyi": "b86769987ef4d1cbdddb461be88539fd", | ||
| "reflex/components/datadisplay/dataeditor.pyi": "fb26f3e702fcb885539d1cf82a854be3", | ||
| "reflex/components/datadisplay/shiki_code_block.pyi": "1d53e75b6be0d3385a342e7b3011babd", | ||
| "reflex/components/el/__init__.pyi": "0adfd001a926a2a40aee94f6fa725ecc", | ||
| "reflex/components/el/element.pyi": "c5974a92fbc310e42d0f6cfdd13472f4", | ||
| "reflex/components/el/elements/__init__.pyi": "29512d7a6b29c6dc5ff68d3b31f26528", | ||
| "reflex/components/el/elements/base.pyi": "3f74c7ea573ea29b055b0cd48b040d2c", | ||
| "reflex/components/el/elements/forms.pyi": "8b6bb2fbaf4bad828b076e2f7c8444d0", | ||
| "reflex/components/el/elements/inline.pyi": "3549cd6ad45217aa6387800911b641c3", | ||
| "reflex/components/el/elements/media.pyi": "9b97220aa99783d402b6e278c4069043", | ||
| "reflex/components/el/elements/metadata.pyi": "24448004b7aa07f1225028a85bd49fef", | ||
| "reflex/components/el/elements/other.pyi": "0c4d5d0b955d8596bf6cf4a48d7decdb", | ||
| "reflex/components/el/elements/scripts.pyi": "d33df9f21f7e838376b2b5024beef7c9", | ||
| "reflex/components/el/elements/sectioning.pyi": "3c5a7e4caa9c25da0ae788f02466eac4", | ||
| "reflex/components/el/elements/tables.pyi": "686eb70ea7d8c4dafb0cc5c284e76184", | ||
| "reflex/components/el/elements/typography.pyi": "684e83dde887dba12badd0fb75c87c04", | ||
| "reflex/components/gridjs/datatable.pyi": "98a7e1b3f3b60cafcdfcd8879750ee42", | ||
| "reflex/components/lucide/icon.pyi": "9cdd1107295f5c4b6d5d6516f487f237", | ||
| "reflex/components/markdown/markdown.pyi": "dd74e8e9665b2a813ff799a7aa190b44", | ||
| "reflex/components/moment/moment.pyi": "e1952f1c2c82cef85d91e970d1be64ab", | ||
| "reflex/components/plotly/plotly.pyi": "4311a0aae2abcc9226abb6a273f96372", | ||
| "reflex/components/radix/__init__.pyi": "5d8e3579912473e563676bfc71f29191", | ||
| "reflex/components/radix/primitives/__init__.pyi": "01c388fe7a1f5426a16676404344edf6", | ||
| "reflex/components/radix/primitives/accordion.pyi": "19484eca0ad53f538f5db04c09921738", | ||
| "reflex/components/radix/primitives/base.pyi": "9ef34884fb6028dc017df5e2db639c81", | ||
| "reflex/components/radix/primitives/dialog.pyi": "9ee73362bb59619c482b6b0d07033f37", | ||
| "reflex/components/radix/primitives/drawer.pyi": "921e45dfaf5b9131ef27c561c3acca2e", | ||
| "reflex/components/radix/primitives/form.pyi": "78055e820703c98c3b838aa889566365", | ||
| "reflex/components/radix/primitives/progress.pyi": "c917952d57ddb3e138a40c4005120d5e", | ||
| "reflex/components/radix/primitives/slider.pyi": "4ff06f0025d47f166132909b09ab96f8", | ||
| "reflex/components/radix/themes/__init__.pyi": "582b4a7ead62b2ae8605e17fa084c063", | ||
| "reflex/components/radix/themes/base.pyi": "3e1ccd5ce5fef0b2898025193ee3d069", | ||
| "reflex/components/radix/themes/color_mode.pyi": "dda570583355d8c0d8f607be457ba7a1", | ||
| "reflex/components/radix/themes/components/__init__.pyi": "efa279ee05479d7bb8a64d49da808d03", | ||
| "reflex/components/radix/themes/components/alert_dialog.pyi": "eed422fcc1ff5ccf3dbf6934699bd0b1", | ||
| "reflex/components/radix/themes/components/aspect_ratio.pyi": "71de4160d79840561c48b570197a4152", | ||
| "reflex/components/radix/themes/components/avatar.pyi": "e40c2f0fda6d2c028d83681a27f3fb96", | ||
| "reflex/components/radix/themes/components/badge.pyi": "58fd1a9c5d2f8762e2a0370311731ff5", | ||
| "reflex/components/radix/themes/components/button.pyi": "50f0b08ad5d1d1054ab537152f0f5c43", | ||
| "reflex/components/radix/themes/components/callout.pyi": "547f2570ffbd10db36b745566e9f1b17", | ||
| "reflex/components/radix/themes/components/card.pyi": "f7adb83f7b001a11bdd7fd6791fb3ffb", | ||
| "reflex/components/radix/themes/components/checkbox.pyi": "8eabb6887a5d0849a43e086a284814c2", | ||
| "reflex/components/radix/themes/components/checkbox_cards.pyi": "1d567fd04b4425abd5cc5aad10108aa9", | ||
| "reflex/components/radix/themes/components/checkbox_group.pyi": "8638582a623036f8893a3fa6080f2672", | ||
| "reflex/components/radix/themes/components/context_menu.pyi": "b9499d8bdd2c5565621fea5fe7d7a25a", | ||
| "reflex/components/radix/themes/components/data_list.pyi": "6f8d9c582e084c23966b992158193b72", | ||
| "reflex/components/radix/themes/components/dialog.pyi": "d2615f1a68c80ff930444d054b598c13", | ||
| "reflex/components/radix/themes/components/dropdown_menu.pyi": "43f8770c9adf93c73398d68f79048424", | ||
| "reflex/components/radix/themes/components/hover_card.pyi": "a96f4433237f9994decf935deff9f269", | ||
| "reflex/components/radix/themes/components/icon_button.pyi": "e930911d8ecbe61e5447e61c76a28ab6", | ||
| "reflex/components/radix/themes/components/inset.pyi": "bd7a2186b553bd4c86d83ff50c784066", | ||
| "reflex/components/radix/themes/components/popover.pyi": "91f8edefeb232cc6d48690b1838144c2", | ||
| "reflex/components/radix/themes/components/progress.pyi": "0e59587d5b3c8fe0d0067587f144e5b0", | ||
| "reflex/components/radix/themes/components/radio.pyi": "f375aa5ac746679618ea7dad257e3224", | ||
| "reflex/components/radix/themes/components/radio_cards.pyi": "9dc34a1ce2a1924eb1f41438ef84e80b", | ||
| "reflex/components/radix/themes/components/radio_group.pyi": "173254cf91908bcf6aa4fa21a747e2cf", | ||
| "reflex/components/radix/themes/components/scroll_area.pyi": "2e3539b0f6895dda127ee96e9864dbf9", | ||
| "reflex/components/radix/themes/components/segmented_control.pyi": "1776f1ad936bae402007802b1ee98906", | ||
| "reflex/components/radix/themes/components/select.pyi": "2c7aee592972ff5f05da08154aa981c8", | ||
| "reflex/components/radix/themes/components/separator.pyi": "79e550cc10ee455f35d75d0e236fedd2", | ||
| "reflex/components/radix/themes/components/skeleton.pyi": "a25d3ceb56f99f736ea463579845c454", | ||
| "reflex/components/radix/themes/components/slider.pyi": "305a34c14ca8656ca9267e4c31aaa388", | ||
| "reflex/components/radix/themes/components/spinner.pyi": "b7e689e7d75635e379242fd113a1ea9a", | ||
| "reflex/components/radix/themes/components/switch.pyi": "f1ba948750a74126cda990e89a3ec7ef", | ||
| "reflex/components/radix/themes/components/table.pyi": "eefbbd1904deae3d166fcad28b20fd4a", | ||
| "reflex/components/radix/themes/components/tabs.pyi": "a533d2509a6798fe0ab7275b0152519d", | ||
| "reflex/components/radix/themes/components/text_area.pyi": "4af55e5d18a5b9d56717bf31b23ea543", | ||
| "reflex/components/radix/themes/components/text_field.pyi": "232618b744076db98d861ea1b9eb3192", | ||
| "reflex/components/radix/themes/components/tooltip.pyi": "2b8366200ce92ec4784ca3ec4152e676", | ||
| "reflex/components/radix/themes/layout/__init__.pyi": "73eefc509a49215b1797b5b5d28d035e", | ||
| "reflex/components/radix/themes/layout/base.pyi": "5be31d7dadd23ab544e53762423d123e", | ||
| "reflex/components/radix/themes/layout/box.pyi": "dbaed1c50c668805fc7b71d22f878254", | ||
| "reflex/components/radix/themes/layout/center.pyi": "17323694217e8ad7611adb683f8d96ce", | ||
| "reflex/components/radix/themes/layout/container.pyi": "24222fd7ffa2dc05f709eab6c7b9643c", | ||
| "reflex/components/radix/themes/layout/flex.pyi": "0307e9dbe6a5784140121d77c8f67a86", | ||
| "reflex/components/radix/themes/layout/grid.pyi": "95c9edb8bdd4e39dc1bd6bc2a8ca0933", | ||
| "reflex/components/radix/themes/layout/list.pyi": "049ecf827ef0ba8de2d76dbf7b1c562c", | ||
| "reflex/components/radix/themes/layout/section.pyi": "a51952b9b5c8227aa3024373dedcad5d", | ||
| "reflex/components/radix/themes/layout/spacer.pyi": "c35accf0f2f742c90a23675ff1fb960d", | ||
| "reflex/components/radix/themes/layout/stack.pyi": "271d3315c6196356d3ced759520d4e7d", | ||
| "reflex/components/radix/themes/typography/__init__.pyi": "b8ef970530397e9984004961f3aaee62", | ||
| "reflex/components/radix/themes/typography/blockquote.pyi": "080c71899532f5dbf4cf143e7a5ad3bf", | ||
| "reflex/components/radix/themes/typography/code.pyi": "7ffe785d55979cf8ff97ea040f3e2b64", | ||
| "reflex/components/radix/themes/typography/heading.pyi": "0ebb38915cd0521fd59c569e04d288bb", | ||
| "reflex/components/radix/themes/typography/link.pyi": "e88c5d880a54548b6808c097ac62505b", | ||
| "reflex/components/radix/themes/typography/text.pyi": "50f9ca15a941e4b77ddd12e77aa3c03e", | ||
| "reflex/components/react_player/audio.pyi": "0e1690ff1f1f39bc748278d292238350", | ||
| "reflex/components/react_player/react_player.pyi": "5ccd373b94ed1d3934ae6afc46bd6fe4", | ||
| "reflex/components/react_player/video.pyi": "998671c06103d797c554d9278eb3b2a0", | ||
| "reflex/components/react_router/dom.pyi": "3042fa630b7e26a7378fe045d7fbf4af", | ||
| "reflex/components/recharts/__init__.pyi": "6ee7f1ca2c0912f389ba6f3251a74d99", | ||
| "reflex/components/recharts/cartesian.pyi": "cfca4f880239ffaecdf9fb4c7c8caed5", | ||
| "reflex/components/recharts/charts.pyi": "013036b9c00ad85a570efdb813c1bc40", | ||
| "reflex/components/recharts/general.pyi": "d87ff9b85b2a204be01753690df4fb11", | ||
| "reflex/components/recharts/polar.pyi": "ad24bd37c6acc0bc9bd4ac01af3ffe49", | ||
| "reflex/components/recharts/recharts.pyi": "c41d19ab67972246c574098929bea7ea", | ||
| "reflex/components/sonner/toast.pyi": "3c27bad1aaeb5183eaa6a41e77e8d7f0" | ||
| "reflex/components/gridjs/datatable.pyi": "e1f34ade3873a931770da4a35586f298" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| """Table components.""" | ||
| """Table components for Reflex using Gridjs.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
|
|
@@ -14,58 +14,52 @@ | |
|
|
||
|
|
||
| class Gridjs(NoSSRComponent): | ||
| """A component that wraps a nivo bar component.""" | ||
| """A base component that wraps Gridjs (JS library) for tables.""" | ||
|
|
||
| library = "gridjs-react@6.1.1" | ||
|
|
||
| lib_dependencies: list[str] = ["gridjs@6.2.0"] | ||
|
|
||
|
|
||
| class DataTable(Gridjs): | ||
| """A data table component.""" | ||
| """A flexible data table component for Reflex. | ||
|
|
||
| tag = "Grid" | ||
| Supports: | ||
| - Pandas DataFrames | ||
| - Python lists | ||
| - Reflex Vars (state variables) | ||
| """ | ||
|
|
||
| tag = "Grid" | ||
| alias = "DataTableGrid" | ||
|
|
||
| # The data to display. Either a list of lists or a pandas dataframe. | ||
| data: Any | ||
|
|
||
| # The list of columns to display. Required if data is a list and should not be provided | ||
| # if the data field is a dataframe | ||
| columns: Var[Sequence] | ||
|
|
||
| # Enable a search bar. | ||
| search: Var[bool] | ||
|
|
||
| # Enable sorting on columns. | ||
| sort: Var[bool] | ||
|
|
||
| # Enable resizable columns. | ||
| resizable: Var[bool] | ||
|
|
||
| # Enable pagination. | ||
| pagination: Var[bool | dict] | ||
|
|
||
| # ----------------------------- | ||
| # Component Props | ||
| # ----------------------------- | ||
| data: Any # The data to display (list of lists or DataFrame) | ||
| columns: Var[Sequence] # Columns to display (optional if using DataFrame) | ||
| search: Var[bool] # Enable search | ||
| sort: Var[bool] # Enable sorting | ||
| resizable: Var[bool] # Enable column resizing | ||
| pagination: Var[bool | dict] # Enable pagination | ||
|
Comment on lines
+35
to
+43
Collaborator
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. these should not be reformatted; our doc generator specifically looks for comments preceeding props, so if you write it like this, our doc generator breaks |
||
|
|
||
| # ----------------------------- | ||
| # Component creation | ||
| # ----------------------------- | ||
|
Comment on lines
+45
to
+47
Collaborator
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. reflex code style avoids these kind of fancy block comments |
||
| @classmethod | ||
| def create(cls, *children, **props): | ||
| """Create a datatable component. | ||
| """Create a DataTable component with proper validation. | ||
|
|
||
| Args: | ||
| *children: The children of the component. | ||
| **props: The props to pass to the component. | ||
| Raises: | ||
| ValueError: If both DataFrame and columns are provided, or | ||
| if columns are missing for a list-type data field. | ||
|
|
||
| Returns: | ||
| The datatable component. | ||
|
|
||
| Raises: | ||
| ValueError: If a pandas dataframe is passed in and columns are also provided. | ||
| DataTable: The created DataTable component. | ||
| """ | ||
| data = props.get("data") | ||
| columns = props.get("columns") | ||
|
|
||
| # The annotation should be provided if data is a computed var. We need this to know how to | ||
| # render pandas dataframes. | ||
| # 1️⃣ Ensure computed Vars have type annotations | ||
|
Collaborator
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. Remove the emoji from the comments |
||
| if is_computed_var(data) and data._var_type == Any: | ||
| msg = "Annotation of the computed var assigned to the data field should be provided." | ||
| raise ValueError(msg) | ||
|
|
@@ -78,38 +72,50 @@ def create(cls, *children, **props): | |
| msg = "Annotation of the computed var assigned to the column field should be provided." | ||
| raise ValueError(msg) | ||
|
|
||
| # If data is a pandas dataframe and columns are provided throw an error. | ||
| # 2️⃣ Disallow DataFrame + columns (columns auto-detected from DataFrame) | ||
| if ( | ||
| types.is_dataframe(type(data)) | ||
| or (isinstance(data, Var) and types.is_dataframe(data._var_type)) | ||
| ) and columns is not None: | ||
| msg = "Cannot pass in both a pandas dataframe and columns to the data_table component." | ||
| raise ValueError(msg) | ||
|
|
||
| # If data is a list and columns are not provided, throw an error | ||
| # 3️⃣ Require columns if data is a list | ||
| if ( | ||
| (isinstance(data, Var) and types.typehint_issubclass(data._var_type, list)) | ||
| or isinstance(data, list) | ||
| ) and columns is None: | ||
| msg = "column field should be specified when the data field is a list type" | ||
| msg = "Column field should be specified when the data field is a list type" | ||
| raise ValueError(msg) | ||
|
|
||
| # Create the component. | ||
| return super().create( | ||
| *children, | ||
| **props, | ||
| ) | ||
| # 4️⃣ Call parent create method | ||
| return super().create(*children, **props) | ||
|
|
||
| # ----------------------------- | ||
| # Add external imports (CSS) | ||
| # ----------------------------- | ||
| def add_imports(self) -> ImportDict: | ||
| """Add the imports for the datatable component. | ||
| """Add CSS for Gridjs. | ||
|
|
||
| Returns: | ||
| The import dict for the component. | ||
| ImportDict: The import dictionary required for the component. | ||
| """ | ||
| return {"": "gridjs/dist/theme/mermaid.css"} | ||
|
|
||
| # ----------------------------- | ||
| # Render component | ||
| # ----------------------------- | ||
| def _render(self) -> Tag: | ||
| """Normalize columns and prepare data for front-end rendering. | ||
|
|
||
| Returns: | ||
| Tag: The rendered table component. | ||
| """ | ||
| # ----------------------------- | ||
| # Case 1: DataFrame coming from State (Var) | ||
| # ----------------------------- | ||
| if isinstance(self.data, Var) and types.is_dataframe(self.data._var_type): | ||
| # Convert DataFrame to front-end-safe Vars | ||
| self.columns = self.data._replace( | ||
| _js_expr=f"{self.data._js_expr}.columns", | ||
| _var_type=list[Any], | ||
|
|
@@ -118,14 +124,53 @@ def _render(self) -> Tag: | |
| _js_expr=f"{self.data._js_expr}.data", | ||
| _var_type=list[list[Any]], | ||
| ) | ||
|
|
||
| # ----------------------------- | ||
| # Case 2: DataFrame passed directly from Python | ||
| # ----------------------------- | ||
| if types.is_dataframe(type(self.data)): | ||
| # If given a pandas df break up the data and columns | ||
| data = serialize(self.data) | ||
| if not isinstance(data, dict): | ||
| msg = "Serialized dataframe should be a dict." | ||
| raise ValueError(msg) | ||
|
|
||
| # Convert Python lists to LiteralVars for front-end rendering | ||
| self.columns = LiteralVar.create(data["columns"]) | ||
| self.data = LiteralVar.create(data["data"]) | ||
|
|
||
| # Render the table. | ||
| # ----------------------------- | ||
| # Case 3: Normalize columns for all other scenarios | ||
| # ----------------------------- | ||
| if self.columns is not None: | ||
| # Python list → LiteralVar | ||
| if isinstance(self.columns, list): | ||
| self.columns = LiteralVar.create([ | ||
| {"id": col, "name": col} if isinstance(col, str) else col | ||
| for col in self.columns | ||
| ]) | ||
|
|
||
| # LiteralVar[list] → normalized LiteralVar | ||
| elif isinstance(self.columns, LiteralVar) and isinstance( | ||
| self.columns._var_value, list | ||
| ): | ||
| self.columns = LiteralVar.create([ | ||
| {"id": col, "name": col} if isinstance(col, str) else col | ||
| for col in self.columns._var_value | ||
| ]) | ||
|
|
||
| # Var[list] → frontend-safe JS mapping (compile-time + runtime safe) | ||
| elif isinstance(self.columns, Var): | ||
| self.columns = self.columns._replace( | ||
| _js_expr=( | ||
| f"{self.columns._js_expr}.map(" | ||
| "(col) => typeof col === 'string' " | ||
| "? ({ id: col, name: col }) " | ||
| ": col)" | ||
| ), | ||
| _var_type=list[Any], | ||
| ) | ||
|
Comment on lines
+163
to
+171
Collaborator
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. prefer to use Var Operations to construct the javascript expression self.columns = self.columns.foreach(
lambda col: rx.cond(col.js_type() == 'string', {"id": col, "name": col}, col)
)The code it generates is not as "pretty", but it's more likely to remain functional as the framework evolves and the code generation changes. |
||
|
|
||
| # ----------------------------- | ||
| # Case 4: Render component | ||
| # ----------------------------- | ||
| return super()._render() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """gridjs component tests.""" |
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.
the other hashes in this file are important too, only the changed files should be updated here