|
| 1 | +--- |
| 2 | +title: "Cell Selection" |
| 3 | +order: 8 |
| 4 | +--- |
| 5 | + |
| 6 | +```python exec |
| 7 | +from pcweb.pages.docs import enterprise |
| 8 | +``` |
| 9 | + |
| 10 | +# Cell Selection |
| 11 | + |
| 12 | +AG Grid provides powerful cell selection capabilities that allow users to select individual cells or ranges of cells. This feature is essential for data manipulation, copying, and advanced interactions like fill handle operations. |
| 13 | + |
| 14 | +## Range Selection |
| 15 | + |
| 16 | +To enable cell selection in your AG Grid, set the `cell_selection` prop to `True`. This automatically enables both single cell selection and range selection capabilities. |
| 17 | + |
| 18 | +### Basic Selection Example |
| 19 | + |
| 20 | +```python demo exec |
| 21 | +import reflex as rx |
| 22 | +import reflex_enterprise as rxe |
| 23 | +import pandas as pd |
| 24 | + |
| 25 | +class CellSelectionState(rx.State): |
| 26 | + data: list[dict] = [] |
| 27 | + |
| 28 | + @rx.event |
| 29 | + def load_data(self): |
| 30 | + df = pd.read_json("https://www.ag-grid.com/example-assets/olympic-winners.json") |
| 31 | + self.data = df.head(10).to_dict("records") |
| 32 | + |
| 33 | + @rx.event |
| 34 | + def echo_selection(self, ranges: list[dict], started: bool, finished: bool): |
| 35 | + if finished: |
| 36 | + yield rx.toast(f"Selected ranges: {ranges}") |
| 37 | + |
| 38 | +column_defs = [ |
| 39 | + {"field": "athlete", "width": 150}, |
| 40 | + {"field": "age", "width": 90}, |
| 41 | + {"field": "country", "width": 120}, |
| 42 | + {"field": "year", "width": 90}, |
| 43 | + {"field": "sport", "width": 120}, |
| 44 | + {"field": "gold", "width": 100}, |
| 45 | + {"field": "silver", "width": 100}, |
| 46 | + {"field": "bronze", "width": 100}, |
| 47 | +] |
| 48 | + |
| 49 | +def basic_cell_selection(): |
| 50 | + return rx.vstack( |
| 51 | + rx.text("Click and drag to select cells. Selection info will appear in a toast.", size="2"), |
| 52 | + rxe.ag_grid( |
| 53 | + id="basic_cell_selection_grid", |
| 54 | + column_defs=column_defs, |
| 55 | + row_data=CellSelectionState.data, |
| 56 | + cell_selection=True, |
| 57 | + on_cell_selection_changed=CellSelectionState.echo_selection, |
| 58 | + width="100%", |
| 59 | + height="400px", |
| 60 | + ), |
| 61 | + on_mount=CellSelectionState.load_data, |
| 62 | + width="100%", |
| 63 | + ) |
| 64 | +``` |
| 65 | + |
| 66 | +### Advanced Selection Event Handling |
| 67 | + |
| 68 | +For more sophisticated selection handling, you can process the selection ranges to calculate detailed information: |
| 69 | + |
| 70 | +```python demo exec |
| 71 | +import reflex as rx |
| 72 | +import reflex_enterprise as rxe |
| 73 | +import pandas as pd |
| 74 | + |
| 75 | +class AdvancedSelectionState(rx.State): |
| 76 | + data: list[dict] = [] |
| 77 | + |
| 78 | + @rx.event |
| 79 | + def load_data(self): |
| 80 | + df = pd.DataFrame({ |
| 81 | + "name": ["Alice", "Bob", "Charlie", "Diana", "Eve"], |
| 82 | + "score": [85, 92, 78, 96, 88], |
| 83 | + "grade": ["B", "A", "C", "A", "B"], |
| 84 | + "attempts": [3, 2, 4, 1, 3] |
| 85 | + }) |
| 86 | + self.data = df.to_dict("records") |
| 87 | + |
| 88 | + @rx.event |
| 89 | + def handle_selection(self, ranges: list[dict], started: bool, finished: bool): |
| 90 | + if finished and ranges: |
| 91 | + total_cells = sum( |
| 92 | + (r.get("endRow", 0) - r.get("startRow", 0) + 1) * |
| 93 | + len(r.get("columns", [])) |
| 94 | + for r in ranges |
| 95 | + ) |
| 96 | + yield rx.toast(f"Selected {total_cells} cells across {len(ranges)} ranges") |
| 97 | + |
| 98 | +editable_column_defs = [ |
| 99 | + {"field": "name", "width": 120}, |
| 100 | + {"field": "score", "width": 100, "editable": True}, |
| 101 | + {"field": "grade", "width": 100, "editable": True}, |
| 102 | + {"field": "attempts", "width": 120, "editable": True}, |
| 103 | +] |
| 104 | + |
| 105 | +def advanced_selection_example(): |
| 106 | + return rx.vstack( |
| 107 | + rx.text("Select ranges of cells. Try selecting multiple ranges by holding Ctrl/Cmd.", size="2"), |
| 108 | + rxe.ag_grid( |
| 109 | + id="advanced_selection_grid", |
| 110 | + column_defs=editable_column_defs, |
| 111 | + row_data=AdvancedSelectionState.data, |
| 112 | + cell_selection=True, |
| 113 | + on_cell_selection_changed=AdvancedSelectionState.handle_selection, |
| 114 | + width="100%", |
| 115 | + height="300px", |
| 116 | + ), |
| 117 | + on_mount=AdvancedSelectionState.load_data, |
| 118 | + width="100%", |
| 119 | + ) |
| 120 | +``` |
| 121 | + |
| 122 | +## Fill Handle |
| 123 | + |
| 124 | +The fill handle is a powerful feature that allows users to quickly fill cells by dragging from a selected cell or range. When enabled, a small square appears at the bottom-right corner of the selection that users can drag to fill adjacent cells. |
| 125 | + |
| 126 | +### Enabling Fill Handle |
| 127 | + |
| 128 | +To enable the fill handle, configure the `cell_selection` prop with a dictionary containing the handle configuration: |
| 129 | + |
| 130 | +```python |
| 131 | +cell_selection={ |
| 132 | + "handle": { |
| 133 | + "mode": "fill", # Enable fill handle |
| 134 | + } |
| 135 | +} |
| 136 | +``` |
| 137 | + |
| 138 | +### Fill Handle Events |
| 139 | + |
| 140 | +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. |
| 141 | + |
| 142 | +```python demo exec |
| 143 | +import reflex as rx |
| 144 | +import reflex_enterprise as rxe |
| 145 | +import pandas as pd |
| 146 | + |
| 147 | +class FillHandleState(rx.State): |
| 148 | + data: list[dict] = [] |
| 149 | + change_log: list[str] = [] |
| 150 | + |
| 151 | + @rx.event |
| 152 | + def load_data(self): |
| 153 | + df = pd.DataFrame({ |
| 154 | + "item": ["Apple", "Banana", "Cherry", "Date", "Elderberry"], |
| 155 | + "quantity": [10, 15, 8, 12, 20], |
| 156 | + "price": [1.50, 0.75, 2.00, 3.00, 4.50], |
| 157 | + "total": [15.00, 11.25, 16.00, 36.00, 90.00] |
| 158 | + }) |
| 159 | + self.data = df.to_dict("records") |
| 160 | + |
| 161 | + @rx.event |
| 162 | + def handle_cell_change(self, data: dict): |
| 163 | + row_index = data.get("rowIndex", 0) |
| 164 | + field = data.get("colId", "") |
| 165 | + new_value = data.get("newValue", "") |
| 166 | + old_value = data.get("oldValue", "") |
| 167 | + |
| 168 | + change_msg = f"Row {row_index + 1}, {field}: '{old_value}' → '{new_value}'" |
| 169 | + self.change_log = [change_msg] + self.change_log[:9] # Keep last 10 changes |
| 170 | + |
| 171 | + # Update the data |
| 172 | + if 0 <= row_index < len(self.data): |
| 173 | + self.data[row_index][field] = new_value |
| 174 | + |
| 175 | +fill_column_defs = [ |
| 176 | + {"field": "item", "width": 120}, |
| 177 | + {"field": "quantity", "width": 100, "editable": True, "type": "numericColumn"}, |
| 178 | + {"field": "price", "width": 100, "editable": True, "type": "numericColumn"}, |
| 179 | + {"field": "total", "width": 100, "editable": True, "type": "numericColumn"}, |
| 180 | +] |
| 181 | + |
| 182 | +def fill_handle_example(): |
| 183 | + return rx.vstack( |
| 184 | + rx.text("Select a cell and drag the fill handle (small square at bottom-right) to fill adjacent cells.", size="2"), |
| 185 | + rxe.ag_grid( |
| 186 | + id="fill_handle_grid", |
| 187 | + column_defs=fill_column_defs, |
| 188 | + row_data=FillHandleState.data, |
| 189 | + cell_selection={ |
| 190 | + "handle": { |
| 191 | + "mode": "fill", # Enable fill handle |
| 192 | + } |
| 193 | + }, |
| 194 | + on_cell_value_changed=FillHandleState.handle_cell_change, |
| 195 | + width="100%", |
| 196 | + height="300px", |
| 197 | + ), |
| 198 | + rx.divider(), |
| 199 | + rx.text("Recent Changes:", weight="bold", size="3"), |
| 200 | + rx.cond( |
| 201 | + FillHandleState.change_log, |
| 202 | + rx.vstack( |
| 203 | + rx.foreach( |
| 204 | + FillHandleState.change_log, |
| 205 | + lambda change: rx.text(change, size="1", color="gray") |
| 206 | + ), |
| 207 | + spacing="1", |
| 208 | + ), |
| 209 | + rx.text("No changes yet", size="2", color="gray") |
| 210 | + ), |
| 211 | + on_mount=FillHandleState.load_data, |
| 212 | + width="100%", |
| 213 | + spacing="4", |
| 214 | + ) |
| 215 | +``` |
| 216 | + |
| 217 | +## Advanced Configuration Options |
| 218 | + |
| 219 | +You can further customize cell selection behavior using additional configuration options: |
| 220 | + |
| 221 | +```python demo exec |
| 222 | +import reflex as rx |
| 223 | +import reflex_enterprise as rxe |
| 224 | +import pandas as pd |
| 225 | + |
| 226 | +class ConfigurationState(rx.State): |
| 227 | + data: list[dict] = [] |
| 228 | + |
| 229 | + @rx.event |
| 230 | + def load_data(self): |
| 231 | + df = pd.DataFrame({ |
| 232 | + "id": range(1, 8), |
| 233 | + "name": ["Product A", "Product B", "Product C", "Product D", "Product E", "Product F", "Product G"], |
| 234 | + "category": ["Electronics", "Clothing", "Electronics", "Books", "Clothing", "Electronics", "Books"], |
| 235 | + "price": [299.99, 49.99, 199.99, 24.99, 79.99, 399.99, 19.99], |
| 236 | + "stock": [15, 32, 8, 45, 23, 12, 67] |
| 237 | + }) |
| 238 | + self.data = df.to_dict("records") |
| 239 | + |
| 240 | +configuration_column_defs = [ |
| 241 | + {"field": "id", "width": 80}, |
| 242 | + {"field": "name", "width": 150, "editable": True}, |
| 243 | + {"field": "category", "width": 120}, |
| 244 | + {"field": "price", "width": 100, "editable": True, "type": "numericColumn"}, |
| 245 | + {"field": "stock", "width": 100, "editable": True, "type": "numericColumn"}, |
| 246 | +] |
| 247 | + |
| 248 | +def configuration_example(): |
| 249 | + return rx.vstack( |
| 250 | + rx.text("Cell selection with additional configuration options", size="2"), |
| 251 | + rxe.ag_grid( |
| 252 | + id="configuration_grid", |
| 253 | + column_defs=configuration_column_defs, |
| 254 | + row_data=ConfigurationState.data, |
| 255 | + cell_selection={ |
| 256 | + "handle": { |
| 257 | + "mode": "fill", |
| 258 | + } |
| 259 | + }, |
| 260 | + enable_cell_text_selection=True, # Allow text selection within cells |
| 261 | + suppress_cell_focus=False, # Allow cell focus |
| 262 | + width="100%", |
| 263 | + height="350px", |
| 264 | + ), |
| 265 | + on_mount=ConfigurationState.load_data, |
| 266 | + width="100%", |
| 267 | + ) |
| 268 | +``` |
| 269 | + |
| 270 | +## Key Features |
| 271 | + |
| 272 | +- **Cell Selection**: Enable with `cell_selection=True` for both single cell and range selection capabilities |
| 273 | +- **Fill Handle**: Configure with `cell_selection={"handle": {"mode": "fill"}}` for drag-to-fill functionality |
| 274 | +- **Event Handling**: Use `on_cell_selection_changed` to respond to selection changes |
| 275 | +- **Value Changes**: Use `on_cell_value_changed` to handle individual cell edits and fill operations |
| 276 | +- **Text Selection**: Enable `enable_cell_text_selection=True` to allow text selection within cells |
| 277 | + |
| 278 | +## Best Practices |
| 279 | + |
| 280 | +1. **Use cell_selection configuration**: Both single cell and range selection are automatically enabled with `cell_selection=True`, providing all necessary selection capabilities for fill operations. |
| 281 | + |
| 282 | +2. **Handle cell value changes**: When using fill handle, implement `on_cell_value_changed` to process the data updates in your backend. |
| 283 | + |
| 284 | +3. **Provide user feedback**: Use toasts or other UI elements to confirm selection actions and data changes. |
| 285 | + |
| 286 | +4. **Consider performance**: For large datasets, be mindful of the performance impact of frequent cell value change events. |
| 287 | + |
| 288 | +5. **Validate fill operations**: Implement validation logic in your `on_cell_value_changed` handler to ensure data integrity. |
0 commit comments