Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/app/reflex_docs/pages/docs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ def get_previews_from_frontmatter(filepath: str) -> dict[str, str]:
"docs/enterprise/ag_grid/model-wrapper.md": "AG Grid with a Pandas DataFrame in Python",
"docs/enterprise/ag_grid/value-transformers.md": "AG Grid Value Transformers in Python",
"docs/enterprise/ag_grid/aligned-grids.md": "AG Grid Aligned Grids in Python",
"docs/enterprise/ag_grid/tree-data.md": "AG Grid Tree Data in Python",
"docs/enterprise/ag_grid/master-detail.md": "AG Grid Master Detail in Python",
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ def get_sidebar_items_enterprise_components():
names="Pivot Mode",
link=enterprise.ag_grid.pivot_mode.path,
),
SideBarItem(
names="Tree Data",
link=enterprise.ag_grid.tree_data.path,
),
SideBarItem(
names="Master Detail",
link=enterprise.ag_grid.master_detail.path,
),
SideBarItem(
names="Theme",
link=enterprise.ag_grid.theme.path,
Expand Down
17 changes: 16 additions & 1 deletion docs/enterprise/ag_grid/cell-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,27 @@ To enable the fill handle, configure the `cell_selection` prop with a dictionary

```python
cell_selection = {
"mode": "multiCell", # or "singleCell" to restrict selection to one cell
"handle": {
"mode": "fill", # Enable fill handle
}
"direction": "xy", # "x" (horizontal), "y" (vertical), or "xy" (both)
},
}
```

The fill handle is configured entirely through `cell_selection` — props like `enable_fill_handle`, `fill_handle`, or `grid_options={"enableFillHandle": True}` do not exist. Similarly, range selection is enabled with `cell_selection=True`, not `enable_range_selection`.

To exclude specific columns from fill operations (typically text columns where an incremental series makes no sense), set `suppress_fill_handle: True` on the column definition:

```python
column_defs = [
{"field": "athlete", "suppress_fill_handle": True},
{"field": "age", "editable": True, "type": "numericColumn"},
]
```

Fill behavior depends on the data type: numbers extend as an incremental series, while text and dates are copied to the filled cells.

### Fill Handle Events

When using the fill handle, it will trigger `on_cell_value_changed` for each cell receiving a fill value. This allows your backend to handle the data changes appropriately.
Expand Down
51 changes: 51 additions & 0 deletions docs/enterprise/ag_grid/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,29 @@ def ag_grid_column_filter_types():

📊 **Dataset source:** [GanttChart-updated.csv](https://raw.githubusercontent.com/plotly/datasets/master/GanttChart-updated.csv)

To show an inline filter input below the column headers, set `floating_filter: True` in the column definition (or in `default_col_def` to apply it to every column).

### Multi-Column Filter (Enterprise)

The enterprise `agMultiColumnFilter` combines several filter types on a single column:

```python
column_defs = [
{
"field": "athlete",
"filter": "agMultiColumnFilter",
"filter_params": {
"filters": [
{"filter": "agTextColumnFilter"},
{"filter": "agSetColumnFilter"},
],
},
},
]
```

Enterprise filters may require loading their modules explicitly via the `enterprise_modules` prop — for example `SetFilterModule` for `agSetColumnFilter`, `MultiFilterModule` for `agMultiColumnFilter`, and `FiltersToolPanelModule` for the filter tool panel (shown with `side_bar=True`). See [Functionality you need is not available/working in Reflex](#functionality-you-need-is-not-availableworking-in-reflex) below.

## Row Sorting

By default, the rows can be sorted by any column by clicking on the column header. You can disable sorting of the rows for a column by setting the `sortable` key to `False` in the column definition.
Expand Down Expand Up @@ -235,6 +258,34 @@ def ag_grid_simple_row_selection():

📊 **Dataset source:** [gapminder2007.csv](https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv)

### Handling Selection Changes

Use the `on_selection_changed` event trigger to react to selection changes. The event handler receives the selected rows directly as a `list[dict]` — not an event object, so don't try to read the rows from `event["rows"]` or through the grid API:

```python
class GridSelectionState(rx.State):
selected_rows: list[dict] = []

@rx.event
def handle_selection_changed(self, selected_rows: list[dict]):
self.selected_rows = selected_rows
return rx.toast(f"Selected {len(selected_rows)} rows")


def grid_with_selection():
return rxe.ag_grid(
id="selection_grid",
row_data=df.to_dict("records"),
column_defs=column_defs,
row_selection={"mode": "multiRow"},
on_selection_changed=GridSelectionState.handle_selection_changed,
width="100%",
height="40vh",
)
```

An event handler annotated as `def handle(self, event: dict)` raises an `EventHandlerArgTypeMismatchError`, since the trigger passes a `list[dict]`.

## Editing

Enable Editing by setting the `editable` attribute to `True`. The cell editor is inferred from the cell data type. Set the cell editor type using the `cell_editor` attribute.
Expand Down
112 changes: 112 additions & 0 deletions docs/enterprise/ag_grid/master-detail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
meta_description: "Enable master-detail rows in AG Grid with Reflex. Expand rows to reveal a nested detail grid backed by per-row data, configured entirely in Python."
title: Master Detail
---

# Master Detail

Master-detail lets rows expand to show detailed information in a nested grid. Each master row carries its detail rows as a nested list, and an expandable column reveals them.

Three pieces are required:

1. `master_detail=True` on the grid.
2. A column with `"cell_renderer": "agGroupCellRenderer"`, which renders the expand/collapse arrows.
3. `detail_cell_renderer_params` describing the detail grid's columns and how to extract the detail rows from the master row.

```python
import reflex as rx
import reflex_enterprise as rxe


class MasterDetailState(rx.State):
master_data: list[dict] = [
{
"id": 1,
"name": "Product A",
"category": "Electronics",
"price": 299.99,
"counts": [ # Detail rows for this master row
{"count": 10, "value": "Stock Level"},
{"count": 5, "value": "Orders Today"},
{"count": 25, "value": "Total Sales"},
],
},
{
"id": 2,
"name": "Product B",
"category": "Clothing",
"price": 49.99,
"counts": [
{"count": 50, "value": "Stock Level"},
{"count": 12, "value": "Orders Today"},
{"count": 78, "value": "Total Sales"},
],
},
]


column_defs = [
{
"field": "id",
"header_name": "ID",
"width": 80,
"cell_renderer": "agGroupCellRenderer", # Required for expand/collapse
},
{"field": "name", "header_name": "Product Name", "width": 150},
{"field": "category", "header_name": "Category", "width": 120},
{
"field": "price",
"header_name": "Price",
"width": 100,
"value_formatter": "params.value ? '$' + params.value.toFixed(2) : ''",
},
]

detail_cell_renderer_params = {
"detail_grid_options": {
"column_defs": [
{"field": "count", "header_name": "Count"},
{"field": "value", "header_name": "Description"},
]
},
"get_detail_row_data": lambda params: rx.vars.function.FunctionStringVar(
"params.successCallback"
).call(params.data.counts),
}


def master_detail_grid():
return rxe.ag_grid(
id="master_detail_grid",
row_data=MasterDetailState.master_data,
column_defs=column_defs,
master_detail=True,
detail_cell_renderer_params=detail_cell_renderer_params,
width="100%",
height="500px",
)
```

## How Detail Rows Are Provided

`get_detail_row_data` follows AG Grid's asynchronous convention: the grid passes a `params` object containing the master row's `data` and a `successCallback` to invoke with the detail rows. The lambda above calls `params.successCallback` with the nested `counts` list of the expanded row.

The detail grid is a full AG Grid instance with its own `column_defs`, independent from the master grid's columns.

## Static vs State Configuration

Both the column definitions and the detail renderer params can be plain module-level objects or state vars, depending on whether they need to change at runtime:

```python
STATIC_DETAIL_PARAMS = {
"detail_grid_options": {"column_defs": [{"field": "count"}]},
"get_detail_row_data": lambda params: rx.vars.function.FunctionStringVar(
"params.successCallback"
).call(params.data.counts),
}


class State(rx.State):
# Dynamic variant: update these vars to reconfigure the grid
detail_cell_renderer_params: dict = STATIC_DETAIL_PARAMS

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid putting detail callbacks in state

When this dynamic variant is copied with STATIC_DETAIL_PARAMS above, the state var contains the get_detail_row_data lambda. Reflex state vars are serialized for the client; arbitrary callables inside a dict are not preserved, so the detail grid loses the callback and expanded rows cannot load. Keep renderer params with JS callbacks as module-level props or rebuild them in the component instead of storing them in rx.State.

Useful? React with 👍 / 👎.

Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
```
75 changes: 72 additions & 3 deletions docs/enterprise/ag_grid/model-wrapper.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,25 @@ A model wrapper is an utility used to wrap a database model and provide a consis
You can use the basic functionality of the model wrapper by using the `rxe.model_wrapper` function. This function takes a database model and returns a wrapper object that can be used to interact with the model.

```python
import reflex as rx
import reflex_enterprise as rxe


def index_page():
return rxe.model_wrapper(class_model=MyModel)
return rx.box(
rxe.model_wrapper(model_class=MyModel, width="100%"),
height="80vh",
)
```

By default the model_wrapper use the infinite rows model from AgGrid.
By default the model_wrapper use the infinite rows model from AgGrid. As the user scrolls, the wrapper automatically loads windows of rows from the database instead of loading the whole table into memory. The cache size can be tuned with the `max_blocks_in_cache` and `cache_block_size` props.

```md alert warning
# Always place the model wrapper in a container with a calculable height.
If the containing element has no fixed height, the grid will not render. Setting e.g. `height="80vh"` on the parent box is enough.
```

The model passed as `model_class` must exactly match the schema of the database table backing it, or querying will fail. In particular, don't invent a primary key that the actual table doesn't have.

## Custom Model Wrapper

Expand All @@ -41,17 +52,75 @@ In the custom model wrapper, you can override the following methods:

to modify how the model wrapper will behave.

A custom wrapper is rendered with its `create` classmethod:

```python
def index_page():
return rx.box(
MyCustomWrapper.create(model_class=MyModel, width="100%"),
height="80vh",
)
```

### Authorization

By default there is no authentication checking for any database operation performed through the grid — inserts, updates, and deletes are open to any user who can reach the page. To restrict operations, override the `_is_authorized` method in a `ModelWrapper` subclass:

```python
from typing import Sequence

import reflex_enterprise as rxe
from reflex_enterprise.components.ag_grid.wrapper import ModelWrapperActionType


class UserModelWrapper(rxe.ModelWrapper[User]):
async def _is_authorized(
self,
action: ModelWrapperActionType,
action_data: Sequence[User] | dict | None,
) -> bool:
"""Check if the user is authorized to perform the action.

For SELECT, action_data is None.
For INSERT, action_data is a dict of the new row data.
For UPDATE, action_data is a dict of updated row data.
For DELETE, action_data is a list of model objects to delete.
"""
auth_state = await self.get_state(AuthState)
return auth_state.user_is_admin
```

### Customizing Columns and Toolbar

Override `_get_column_defs` to adjust the generated column definitions — for example to disable filtering or sorting on a specific field:

```python
class UserModelWrapper(rxe.ModelWrapper[User]):
def _get_column_defs(self):
cols = super()._get_column_defs()
for col in cols:
if col.field == "internal_notes":
col.filter = None
col.sortable = False
return cols
```

The toolbar UI can be customized as well: override the `_top_toolbar`, `_delete_button`, and `_add_dialog` classmethods to replace the default add/delete controls with your own components.

## SSRM Model Wrapper

The SSRM model wrapper, used with `rxe.model_wrapper_ssrm`, is a version of the model wrapper that allows you to use the ServerSideRowModel of AgGrid.

```python
import reflex as rx
import reflex_enterprise as rxe


def index_page():
return rxe.model_wrapper_ssrm(class_model=MyModel)
return rx.box(
rxe.model_wrapper_ssrm(model_class=MyModel, width="100%"),
height="80vh",
)
```

## SSRM Custom Model Wrapper
Expand Down
Loading
Loading