Skip to content

Commit 014575d

Browse files
authored
Merge pull request #421 from BSd3v/v32-maintenance-3
V32 maintenance 3
2 parents f9d1d2a + ed00170 commit 014575d

File tree

6 files changed

+234
-7
lines changed

6 files changed

+234
-7
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,36 @@ All notable changes to `dash-ag-grid` will be documented in this file.
44
This project adheres to [Semantic Versioning](https://semver.org/).
55
Links "DE#nnn" prior to version 2.0 point to the Dash Enterprise closed-source Dash AG Grid repo
66

7+
## [unreleased]
8+
### Fixed
9+
- [#408](https://github.com/plotly/dash-ag-grid/pull/408) fixed issue where the `columnState` would conflict with `columnDefs` updates
10+
- fixes [#416] (https://github.com/plotly/dash-ag-grid/issues/416)
11+
- fixes [#407](https://github.com/plotly/dash-ag-grid/issues/407)
12+
- [#412](https://github.com/plotly/dash-ag-grid/issues/412) fix "Multi-Column Filter not properly recognized in filterParams"
13+
714
## [32.3.2] - 2025-09-17
815

916
### Fixed
1017
- [#403](https://github.com/plotly/dash-ag-grid/issues/403) fix "Maximum update depth exceeded" error
1118

19+
## [32.3.2rc1] - 2025-08-05
20+
21+
### Fixed
22+
- [#394](https://github.com/plotly/dash-ag-grid/issues/394) allow `cellRenderer` column def to be a function
23+
1224
## [32.3.1] - 2025-08-05
1325

1426
### Fixed
1527
- [#394](https://github.com/plotly/dash-ag-grid/issues/394) allow `cellRenderer` column def to be a function
1628

29+
## [33.3.2rc0] - 2025-07-29
30+
31+
### Changed
32+
- bump to v`33.3.2` for the grid
33+
- legacy (CSS-only) themes now require stylesheets to be loaded externally (for example, via the `external_stylesheets` kwarg to the Dash constructor). See `tests/examples/themes_legacy.py` for an example.
34+
- dashGridOptions now accepts a `theme` string or function as per AG Grid's latest theming system. See `tests/examples/themes.py` for examples.
35+
- defaultProps no longer used in modern React versions
36+
1737
## [32.3.0] - 2025-07-23
1838

1939
### Changed

src/lib/fragments/AgGrid.react.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,10 +1361,7 @@ export function DashAgGrid(props) {
13611361

13621362
// Handle columnState push changes
13631363
useEffect(() => {
1364-
if (
1365-
gridApi &&
1366-
(!props.loading_state || prevProps?.loading_state?.is_loading)
1367-
) {
1364+
if (gridApi && !(props.dashRenderType === 'internal')) {
13681365
const existingColumnState = gridApi.getColumnState();
13691366
const realStateChange =
13701367
props.columnState &&
@@ -1620,10 +1617,17 @@ const MemoizedAgGrid = React.memo(DashAgGrid, (prevProps, nextProps) => {
16201617
nextProps.selectedRows,
16211618
prevProps.selectedRows
16221619
);
1620+
const columnStateChanged = !equals(
1621+
nextProps.columnState,
1622+
prevProps.columnState
1623+
);
16231624

16241625
if (
16251626
propsHaveChanged &&
1626-
(!isInternalChange || rowDataChanged || selectedRowsChanged)
1627+
(!isInternalChange ||
1628+
rowDataChanged ||
1629+
selectedRowsChanged ||
1630+
columnStateChanged)
16271631
) {
16281632
return false; // Props changed, re-render
16291633
}

src/lib/utils/propCategories.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ export const COLUMN_NESTED_OR_OBJ_OF_FUNCTIONS = {
298298
export const COLUMN_ARRAY_NESTED_FUNCTIONS = {
299299
children: 1,
300300
filterOptions: 1,
301+
filters: 1,
301302
};
302303

303304
/**

tests/assets/dashAgGridFunctions.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,41 @@ dagfuncs.startWith = ([filterValues], cellValue) => {
396396
const name = cellValue ? cellValue.split(" ")[1] : ""
397397
return name && name.toLowerCase().indexOf(filterValues.toLowerCase()) === 0
398398
}
399+
400+
dagfuncs.dateFilterComparator = (filterLocalDateAtMidnight, cellValue) => {
401+
const dateAsString = cellValue;
402+
403+
if (dateAsString == null) {
404+
// Return -1 to show nulls "before" any date
405+
return -1;
406+
}
407+
408+
// The data from this CSV is in dd/mm/yyyy format
409+
const dateParts = dateAsString.split("/");
410+
if (dateParts.length !== 3) {
411+
// Handle invalid format
412+
return 0;
413+
}
414+
415+
const day = Number(dateParts[0]);
416+
const month = Number(dateParts[1]) - 1; // JS months are 0-indexed
417+
const year = Number(dateParts[2]);
418+
const cellDate = new Date(year, month, day);
419+
420+
// Check for invalid date (e.g., from "NaN")
421+
if (isNaN(cellDate.getTime())) {
422+
return 0;
423+
}
424+
425+
// Now that both parameters are Date objects, we can compare
426+
if (cellDate < filterLocalDateAtMidnight) {
427+
return -1;
428+
} else if (cellDate > filterLocalDateAtMidnight) {
429+
return 1;
430+
}
431+
return 0;
432+
};
433+
399434
// END test_custom_filter.py
400435

401436
// FOR test_quick_filter.py
@@ -502,4 +537,5 @@ dagfuncs.TestEvent = (params, setEventData) => {
502537

503538
dagfuncs.testToyota = (params) => {
504539
return params.data.make == 'Toyota' ? {'color': 'blue'} : {}
505-
}
540+
}
541+

tests/test_column_state.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
from dash import Dash, html, Output, Input, no_update, State, ctx, Patch
1+
from dash import Dash, html, Output, Input, no_update, State, ctx, Patch, dcc
22
import dash_ag_grid as dag
33
import plotly.express as px
44
import json
55
import time
6+
import pandas as pd
67

78
from . import utils
89
from dash.testing.wait import until
@@ -518,3 +519,70 @@ def remove_column(n):
518519
dash_duo.find_element("#remove-column").click()
519520
time.sleep(2) # pausing to emulate separation because user inputs
520521
assert list(filter(lambda i: i.get("level") != "ERROR", dash_duo.get_logs())) == []
522+
523+
def test_toggle_column_visibility(dash_duo):
524+
data = pd.DataFrame([
525+
{"a": 1, "b": 2},
526+
{"a": 3, "b": 4},
527+
])
528+
529+
app = Dash(__name__)
530+
531+
app.layout = html.Div([
532+
dcc.Dropdown(
533+
id="select-columns",
534+
value=list(data.columns),
535+
options=[{"label": col, "value": col} for col in data.columns],
536+
multi=True,
537+
),
538+
dag.AgGrid(
539+
id="ag-grid",
540+
style={"height": "75vh", "width": "100%"},
541+
rowData=data.to_dict(orient="records"),
542+
),
543+
])
544+
545+
@app.callback(
546+
Output("ag-grid", "columnDefs"),
547+
Input("select-columns", "value"),
548+
)
549+
def toggle_column_visibility(selected_columns):
550+
if not selected_columns:
551+
return no_update
552+
return [
553+
{
554+
"headerName": col_name,
555+
"field": col_name,
556+
"hide": col_name not in selected_columns,
557+
}
558+
for col_name in data.columns
559+
]
560+
561+
dash_duo.start_server(app)
562+
563+
# Wait for grid to render
564+
grid = utils.Grid(dash_duo, "ag-grid")
565+
566+
grid.wait_for_cell_text(0, 0, "1")
567+
568+
# Hide column 'b'
569+
dropdown = dash_duo.find_element("#select-columns")
570+
option_b = dash_duo.find_element('span.Select-value-icon:nth-child(1)')
571+
option_b.click()
572+
time.sleep(1)
573+
574+
# Only column 'a' should be visible
575+
grid_headers = dash_duo.find_elements("div.ag-header-cell-label")
576+
header_texts = [h.text for h in grid_headers]
577+
assert "a" not in header_texts
578+
assert "b" in header_texts
579+
580+
# Show both columns again
581+
dropdown.click()
582+
option_b = dash_duo.find_element('.Select-menu')
583+
option_b.click()
584+
time.sleep(1)
585+
grid_headers = dash_duo.find_elements("div.ag-header-cell-label")
586+
header_texts = [h.text for h in grid_headers]
587+
assert "a" in header_texts
588+
assert "b" in header_texts

tests/test_custom_filter.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,101 @@ def test_fi005_custom_filter(dash_duo):
236236
# Test numberParser and numberFormatter
237237
grid.set_filter(0, "$100,5")
238238
grid.wait_for_cell_text(0, 0, "$200,00")
239+
240+
def test_fi006_custom_filter(dash_duo):
241+
app = Dash(__name__)
242+
243+
df = pd.read_csv(
244+
"https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
245+
)
246+
247+
columnDefs = [
248+
{"field": "athlete",
249+
"filter": "agMultiColumnFilter",
250+
"filterParams": {
251+
"filters": [
252+
{"filter": "agTextColumnFilter"},
253+
{"filter": "agSetColumnFilter"} # Example with Set Filter
254+
]
255+
}},
256+
{"field": "country"},
257+
{
258+
"field": "date",
259+
"filter": "agMultiColumnFilter",
260+
"filterParams": {
261+
"filters": [
262+
{
263+
"filter": "agSetColumnFilter",
264+
'filterParams': {'excelMode': 'windows', 'buttons': ['apply', 'reset'],
265+
}
266+
},
267+
{
268+
"filter": "agDateColumnFilter",
269+
'filterParams': {
270+
'excelMode': 'windows',
271+
'buttons': ['apply', 'reset'],
272+
'comparator': {'function': 'dateFilterComparator'},
273+
}
274+
},
275+
],
276+
277+
},
278+
},
279+
]
280+
281+
282+
app.layout = html.Div(
283+
[
284+
dag.AgGrid(
285+
id="date-filter-example",
286+
enableEnterpriseModules=True,
287+
columnDefs=columnDefs,
288+
rowData=df.to_dict("records"),
289+
defaultColDef={"flex": 1, "minWidth": 150, "floatingFilter": True},
290+
dashGridOptions={"animateRows": False}
291+
),
292+
],
293+
)
294+
295+
dash_duo.start_server(app)
296+
297+
grid = utils.Grid(dash_duo, "date-filter-example")
298+
299+
grid.wait_for_cell_text(0, 0, "Michael Phelps")
300+
301+
# Test Set Filter - click filter button on date column
302+
dash_duo.find_element('.ag-floating-filter[aria-colindex="3"] button').click()
303+
304+
# Uncheck "Select All"
305+
dash_duo.find_element('.ag-set-filter-list .ag-set-filter-item .ag-checkbox-input').click()
306+
307+
# Select "24/08/2008"
308+
dash_duo.wait_for_element('.ag-set-filter-list .ag-virtual-list-item', timeout=10)
309+
set_filter_items = dash_duo.find_elements('.ag-set-filter-list .ag-virtual-list-item')
310+
checkboxes = dash_duo.find_elements('.ag-set-filter-list .ag-virtual-list-item .ag-checkbox-input')
311+
312+
for i, item in enumerate(set_filter_items):
313+
if "24/08/2008" in item.text:
314+
checkboxes[i].click()
315+
break
316+
317+
# Apply
318+
dash_duo.find_element('button[data-ref="applyFilterButton"]').click()
319+
grid.wait_for_cell_text(0, 2, "24/08/2008")
320+
321+
# Reset
322+
dash_duo.find_element('.ag-floating-filter[aria-colindex="3"] button').click()
323+
dash_duo.find_element('button[data-ref="resetFilterButton"]').click()
324+
325+
# Test Date Filter - click filter button again
326+
dash_duo.find_element('.ag-floating-filter[aria-colindex="3"] button').click()
327+
328+
# Type date
329+
date_input = dash_duo.find_element('.ag-filter-wrapper .ag-date-filter input[class="ag-input-field-input ag-text-field-input"]')
330+
date_input.click()
331+
date_input.send_keys("08/24/2008")
332+
333+
# Apply
334+
apply_buttons = dash_duo.find_elements('button[data-ref="applyFilterButton"]')
335+
apply_buttons[1].click()
336+
grid.wait_for_cell_text(0, 2, "24/08/2008")

0 commit comments

Comments
 (0)