1- """Table components."""
1+ """Table components for Reflex using Gridjs ."""
22
33from __future__ import annotations
44
1414
1515
1616class Gridjs (NoSSRComponent ):
17- """A component that wraps a nivo bar component ."""
17+ """A base component that wraps Gridjs (JS library) for tables ."""
1818
1919 library = "gridjs-react@6.1.1"
20-
2120 lib_dependencies : list [str ] = ["gridjs@6.2.0" ]
2221
2322
2423class DataTable (Gridjs ):
25- """A data table component."""
24+ """A flexible data table component for Reflex.
2625
27- tag = "Grid"
26+ Supports:
27+ - Pandas DataFrames
28+ - Python lists
29+ - Reflex Vars (state variables)
30+ """
2831
32+ tag = "Grid"
2933 alias = "DataTableGrid"
3034
31- # The data to display. Either a list of lists or a pandas dataframe.
32- data : Any
33-
34- # The list of columns to display. Required if data is a list and should not be provided
35- # if the data field is a dataframe
36- columns : Var [Sequence ]
37-
38- # Enable a search bar.
39- search : Var [bool ]
40-
41- # Enable sorting on columns.
42- sort : Var [bool ]
43-
44- # Enable resizable columns.
45- resizable : Var [bool ]
46-
47- # Enable pagination.
48- pagination : Var [bool | dict ]
49-
35+ # -----------------------------
36+ # Component Props
37+ # -----------------------------
38+ data : Any # The data to display (list of lists or DataFrame)
39+ columns : Var [Sequence ] # Columns to display (optional if using DataFrame)
40+ search : Var [bool ] # Enable search
41+ sort : Var [bool ] # Enable sorting
42+ resizable : Var [bool ] # Enable column resizing
43+ pagination : Var [bool | dict ] # Enable pagination
44+
45+ # -----------------------------
46+ # Component creation
47+ # -----------------------------
5048 @classmethod
5149 def create (cls , * children , ** props ):
52- """Create a datatable component.
50+ """Create a DataTable component with proper validation .
5351
54- Args :
55- *children: The children of the component.
56- **props: The props to pass to the component .
52+ Raises :
53+ ValueError: If both DataFrame and columns are provided, or
54+ if columns are missing for a list-type data field .
5755
5856 Returns:
59- The datatable component.
60-
61- Raises:
62- ValueError: If a pandas dataframe is passed in and columns are also provided.
57+ DataTable: The created DataTable component.
6358 """
6459 data = props .get ("data" )
6560 columns = props .get ("columns" )
6661
67- # The annotation should be provided if data is a computed var. We need this to know how to
68- # render pandas dataframes.
62+ # 1️⃣ Ensure computed Vars have type annotations
6963 if is_computed_var (data ) and data ._var_type == Any :
7064 msg = "Annotation of the computed var assigned to the data field should be provided."
7165 raise ValueError (msg )
@@ -78,38 +72,50 @@ def create(cls, *children, **props):
7872 msg = "Annotation of the computed var assigned to the column field should be provided."
7973 raise ValueError (msg )
8074
81- # If data is a pandas dataframe and columns are provided throw an error.
75+ # 2️⃣ Disallow DataFrame + columns ( columns auto-detected from DataFrame)
8276 if (
8377 types .is_dataframe (type (data ))
8478 or (isinstance (data , Var ) and types .is_dataframe (data ._var_type ))
8579 ) and columns is not None :
8680 msg = "Cannot pass in both a pandas dataframe and columns to the data_table component."
8781 raise ValueError (msg )
8882
89- # If data is a list and columns are not provided, throw an error
83+ # 3️⃣ Require columns if data is a list
9084 if (
9185 (isinstance (data , Var ) and types .typehint_issubclass (data ._var_type , list ))
9286 or isinstance (data , list )
9387 ) and columns is None :
94- msg = "column field should be specified when the data field is a list type"
88+ msg = "Column field should be specified when the data field is a list type"
9589 raise ValueError (msg )
9690
97- # Create the component.
98- return super ().create (
99- * children ,
100- ** props ,
101- )
91+ # 4️⃣ Call parent create method
92+ return super ().create (* children , ** props )
10293
94+ # -----------------------------
95+ # Add external imports (CSS)
96+ # -----------------------------
10397 def add_imports (self ) -> ImportDict :
104- """Add the imports for the datatable component .
98+ """Add CSS for Gridjs .
10599
106100 Returns:
107- The import dict for the component.
101+ ImportDict: The import dictionary required for the component.
108102 """
109103 return {"" : "gridjs/dist/theme/mermaid.css" }
110104
105+ # -----------------------------
106+ # Render component
107+ # -----------------------------
111108 def _render (self ) -> Tag :
109+ """Normalize columns and prepare data for front-end rendering.
110+
111+ Returns:
112+ Tag: The rendered table component.
113+ """
114+ # -----------------------------
115+ # Case 1: DataFrame coming from State (Var)
116+ # -----------------------------
112117 if isinstance (self .data , Var ) and types .is_dataframe (self .data ._var_type ):
118+ # Convert DataFrame to front-end-safe Vars
113119 self .columns = self .data ._replace (
114120 _js_expr = f"{ self .data ._js_expr } .columns" ,
115121 _var_type = list [Any ],
@@ -118,23 +124,53 @@ def _render(self) -> Tag:
118124 _js_expr = f"{ self .data ._js_expr } .data" ,
119125 _var_type = list [list [Any ]],
120126 )
127+
128+ # -----------------------------
129+ # Case 2: DataFrame passed directly from Python
130+ # -----------------------------
121131 if types .is_dataframe (type (self .data )):
122- # If given a pandas df break up the data and columns
123132 data = serialize (self .data )
124133 if not isinstance (data , dict ):
125134 msg = "Serialized dataframe should be a dict."
126135 raise ValueError (msg )
136+
137+ # Convert Python lists to LiteralVars for front-end rendering
127138 self .columns = LiteralVar .create (data ["columns" ])
128139 self .data = LiteralVar .create (data ["data" ])
129140
130- # If columns is a list of strings convert to list of dicts with id and name keys
131- if isinstance (self .columns , LiteralVar ) and isinstance (
132- self .columns ._var_value , list
133- ):
134- self .columns = LiteralVar .create ([
135- {"id" : col , "name" : col } if isinstance (col , str ) else col
136- for col in self .columns ._var_value
137- ])
138-
139- # Render the table.
141+ # -----------------------------
142+ # Case 3: Normalize columns for all other scenarios
143+ # -----------------------------
144+ if self .columns is not None :
145+ # Python list → LiteralVar
146+ if isinstance (self .columns , list ):
147+ self .columns = LiteralVar .create ([
148+ {"id" : col , "name" : col } if isinstance (col , str ) else col
149+ for col in self .columns
150+ ])
151+
152+ # LiteralVar[list] → normalized LiteralVar
153+ elif isinstance (self .columns , LiteralVar ) and isinstance (
154+ self .columns ._var_value , list
155+ ):
156+ self .columns = LiteralVar .create ([
157+ {"id" : col , "name" : col } if isinstance (col , str ) else col
158+ for col in self .columns ._var_value
159+ ])
160+
161+ # Var[list] → frontend-safe JS mapping (compile-time + runtime safe)
162+ elif isinstance (self .columns , Var ):
163+ self .columns = self .columns ._replace (
164+ _js_expr = (
165+ f"{ self .columns ._js_expr } .map("
166+ "(col) => typeof col === 'string' "
167+ "? ({ id: col, name: col }) "
168+ ": col)"
169+ ),
170+ _var_type = list [Any ],
171+ )
172+
173+ # -----------------------------
174+ # Case 4: Render component
175+ # -----------------------------
140176 return super ()._render ()
0 commit comments