Hello,
I have been scratching my head over the following Dash AG Grid issue for the past few days, and thought I would try my luck on the Plotly forum.
I am working on an editable grid, which edited values are stored in a data store. When the user is done editing, they can submit changes to a backend.
Until the changes are submitted, I would like edited cells to be highlighted in a specific color, for the user to easily see what has been edited. Below is an example of the desired behaviour:
After pushing the submit button, the rowData would rerender and the highlights would disappear.
I am struggling with the edition of a single cell style. Through Dash callbacks, I managed to edit the columnDefs to add a background color to a column which has been edited, but I want only to change the cell styling - not the whole column.
The second thing I tried was to play around with ācellFlashDelayā and ācellFadeDelayā (by massively increasing the fade delay), but it felt very hacky and I could not make it work properly anyway.
A potential solution would be to use a clientside callback to add cellStyle to the column and selectively using grid.refreshCells({}) to the (row, column) pair. Unfortunately i have very little experience with Javascript and clientside callbacks, so I have been struggling to making it work. Below is my sample code (dash 2.11.1 and dash-ag-grid 2.2.0):
import dash_ag_grid as dag
import dash
from dash import html, dcc, ctx, Output, Input, State
import pandas as pd
app = dash.Dash(__name__)
df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv")
columnDefs = [
{"field": "country", "sortable": True, "filter": True, "editable": True},
{"field": "year", "sortable": True, "filter": True, "editable": True},
{"field": "athlete", "sortable": True, "filter": True, "editable": True},
{"field": "age", "sortable": True, "filter": True, "editable": True},
{"field": "age_copy", "valueGetter": {'function': 'params.getValue("age")'}},
{"field": "date", "sortable": True, "filter": True, "editable": True},
{"field": "sport", "sortable": True, "filter": True, "editable": True},
{"field": "total", "sortable": True, "filter": True, "editable": True},
]
app.layout = html.Div(
[
html.Button('Submit changes', id='submit-val', n_clicks=0),
dag.AgGrid(
columnDefs=columnDefs,
rowData=df.to_dict("records"),
dashGridOptions={"rowSelection": "multiple",
"enableCellChangeFlash": True,
'cellFlashDelay': 50000,
'cellFadeDelay': 50000,
},
defaultColDef=dict(
resizable=True,
),
id="grouped-grid",
enableEnterpriseModules=True,
getRowId="params.data.date+params.data.athlete",
),
dcc.Store(id='changed_data-store', data=[]),
dcc.Store('flash-trigger-store', data=[]),
dcc.Store(id='callbacktest-store'),
dcc.Store(id='test2-store'),
]
)
@app.callback(
Output("changed_data-store", "data"),
Input("grouped-grid", "cellValueChanged"),
Input("submit-val", "n_clicks"),
State("changed_data-store", "data"),
prevent_initial_call=True,
)
def update_grid(changed_cell, submit_click, store):
"""Save every modification of the grid into a data store, reset the store on changes being submitted to a backend"""
button_id = ctx.triggered_id
if button_id == "submit-val":
print('Changes submitted successfully to the backend. Resetting the store.')
return []
store.append(changed_cell)
print(f'appended cell {changed_cell} to store.')
return store
# first try of a hacky clientside callback (returns a callback error)
app.clientside_callback(
"""
function(n) {
grid = dash_ag_grid.getApi("grouped-grid")
grid.api.flashCells({
rowNodes: [8, 6],
flashDelay: 10000,
fadeDelay: 10000,
})
return {'test': 'test'}
}
""",
Output('flash-trigger-store', 'data'),
Input('grouped-grid', 'cellValueChanged'),
prevent_initial_call=True,
)
# second try of a hacky clientside callback (returns a callback error)
app.clientside_callback(
"""
function onCellClicked(n) {
grid = dash_ag_grid.getApi("grouped-grid")
const colId = n.colId
const rowId = n.rowIndex
colId.cellStyle = { 'background-color': '#f27e57' }
grid.refreshCells({
force: true,
columns: [colId],
rowNodes: [rowId]
})
return {'test': n}
}
""",
Output("callbacktest-store", "data"),
Input("grouped-grid", "cellValueChanged"),
prevent_initial_call=True,
)
@app.callback(
Output("test2-store", "data"),
Input("callbacktest-store", "data"),
prevent_initial_call=True,
)
def check_test_store_value(a):
#Just to check the format of onCellClicked(n)
return a
if __name__ == "__main__":
app.run_server(debug=True)
Using:
app.clientside_callback(
"""
function onCellClicked(n) {
grid = dash_ag_grid.getApi("grouped-grid")
return {'test': n}
}
""",
Output("test-store", "data"),
Input("grouped-grid", "cellValueChanged"),
prevent_initial_call=True,
)
The cell data is of format:
{'rowIndex': 0, 'rowId': '24/08/2008Michael Phelps', 'data': {'athlete': 'Michael Phelps', 'age': '50', 'country': 'United States', 'year': 2008, 'date': '24/08/2008', 'sport': 'Swimming', 'gold': 8, 'silver': 0, 'bronze': 0, 'total': 8}, 'oldValue': 23, 'value': '50', 'colId': 'age', 'timestamp': 1690584026825}
But I did not manage to select rowIndex and colId from there - I am getting callback errors whatever I tried to access the value.
Would anybody have an idea to fix this code or to achieve the desired behaviour in any other way?
Thank you for reading & many thanks to the team for their hard work on the AG Grid port! Itās an incredibly useful package