# Dash AG Grid single cell styling after cell edit

**URL:** <https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266>\
**Category:** Dash Python\
**Tags:** question\
**Created:** [July 28, 2023, 11:19pm UTC](https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266 "2023-07-28T23:19:23Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![Cantelli](https://avatars.discourse-cdn.com/v4/letter/c/3d9bf3/32.png) [@Cantelli](https://community.plotly.com/u/Cantelli)\
**Post date:** [July 28, 2023, 11:19pm UTC](https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266/1 "2023-07-28T23:19:23Z")

</div>

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:

 ![example1](https://us1.discourse-cdn.com/flex024/uploads/plot/original/3X/0/a/0a65f3b399e347c95a968ec30dff9d746e514445.png)

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_):

```auto
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:

```auto
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:

```auto
{'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

---

<div class="post-metadata">

**Author:** ![jinnyzor](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/jinnyzor/32/21194_2.png) [@jinnyzor](https://community.plotly.com/u/jinnyzor)\
**Post date:** [July 29, 2023, 12:38am UTC](https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266/2 "2023-07-29T00:38:18Z")

</div>

Hello @Cantelli,

Welcome to the community.

You can try something that I did here:

> <https://github.com/plotly/dash-ag-grid/blob/main/tests/test_conditional_formatting.py>

And then the function is here:

```auto
dagfuncs.highlightEdits = function(params) {
    if (params.data.changes) {
    if (JSON.parse(params.data.changes).includes(params.colDef.field))
        {return true}
    }
    return false;
}

```

Basically, I added a column to the rowData to test if the column had been edited. If it was in the list, then it changes its styling.

---

<div class="post-metadata">

**Author:** ![Cantelli](https://avatars.discourse-cdn.com/v4/letter/c/3d9bf3/32.png) [@Cantelli](https://community.plotly.com/u/Cantelli)\
**Post date:** [July 29, 2023, 7:30am UTC](https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266/3 "2023-07-29T07:30:19Z")

</div>

Hi @Jinnyzor,

Many thanks for your help, this is a very elegant solution and it works like a charm on simplified examples!

I tried to port it onto the app I am developing; but the grid now fails to load. The issue appears to come from row groups (AG Grid Enterprise), I reckon the aggregate rows ids are not recognised and it leads to callback errors.

Below I adapted your _test\_conditional\_formatting_ example to add a vehicle type column and row aggregate. There are two groups, and two callback errors:

 ![example2](https://us1.discourse-cdn.com/flex024/uploads/plot/original/3X/9/f/9f9aaa8e15cdf962d95fd455f269923875022498.png)

```auto
import dash_ag_grid as dag
from dash import Dash, html, dcc

app = Dash( __name__ )

columnDefs = [
        {
            "headerName": "Type",
            "field": "type",
            "rowGroup": True,
            "cellRenderer": "agGroupCellRenderer",
            "group": True,
        },
        {
            "headerName": "Make",
            "field": "make",
        },
        {
            "headerName": "Model",
            "field": "model",
        },
        {"headerName": "Price", "field": "price"},
        {"field": "changes"},
]

rowData = [
        {"type": "car", "make": "Toyota", "model": "Celica", "price": 35000},
        {"type": "car", "make": "Ford", "model": "Mondeo", "price": 32000},
        {"type": "car", "make": "Porsche", "model": "Boxster", "price": 72000},
        {"type": "truck", "make": "Volvo", "model": "Test1", "price": 60000},
        {"type": "truck", "make": "Volvo", "model": "Test2", "price": 100000},
    ]

cellStyle = {
        "styleConditions": [
            {"condition": "highlightEdits(params)", "style": {"color": "orange"}},
        ]
    }

defaultColDef = {
        "valueSetter": {"function": "addEdits(params)"},
        "editable": True,
        "cellStyle": cellStyle,
    }

getRowStyle = {
        "styleConditions": [
            {"condition": "params.data.make == 'Toyota'", "style": {"color": "blue"}}
        ]
    }

app.layout = html.Div(
        [
            dcc.Markdown(
                "In this grid, the __Make__ column has a popup below the cell, the __Model__ has a popup above the cell, and the __Price__ has the default (in cell) editor."
            ),
            dag.AgGrid(
                enableEnterpriseModules=True,
                columnDefs=columnDefs,
                rowData=rowData,
                defaultColDef=defaultColDef,
                columnSize="sizeToFit",
                getRowStyle=getRowStyle,
                id="grid",
            ),
            html.Button(id="focus"),
        ],
        style={"margin": 20},
    )

if __name__ == " __main__":
    app.run_server(debug=True)

```

It works however if I am just commenting out _enableEnterpriseModules_:

```auto
dag.AgGrid(
                #enableEnterpriseModules=True,
                columnDefs=columnDefs,
                rowData=rowData,
                defaultColDef=defaultColDef,
                columnSize="sizeToFit",
                getRowStyle=getRowStyle,
                id="grid",
            ),

```

and _rowGroup_:

```auto
{
            "headerName": "Type",
            "field": "type",
            #"rowGroup": True,
            "cellRenderer": "agGroupCellRenderer",
            "group": True,
        },

```

 ![example3](https://us1.discourse-cdn.com/flex024/uploads/plot/original/3X/b/0/b09c92fd8c2c6982e4dd63c2f85540e21e35c9c6.png)

I noticed a new version of dash\_ag\_grid ([v2.3.0](https://github.com/plotly/dash-ag-grid/releases/tag/v2.3.0)) which fixes an issue with grouped rows indexing was released this week, so I upgraded - but this didn’t solve the issue.

I reckon there must be a condition to add to handle aggregate rows in **dashAgGridFunctions.js** dagfuncs?

Many thanks again for your help!

---

<div class="post-metadata">

**Author:** ![Cantelli](https://avatars.discourse-cdn.com/v4/letter/c/3d9bf3/32.png) [@Cantelli](https://community.plotly.com/u/Cantelli)\
**Post date:** [July 29, 2023, 10:16am UTC](https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266/4 "2023-07-29T10:16:57Z")

</div>

I found a solution, adding a few conditions on params.data in the js code.

New js functions:

```auto
var dagfuncs = window.dashAgGridFunctions = window.dashAgGridFunctions || {};

dagfuncs.addEdits = function(params) {
    console.log(params); // Debugging line
    if (!params.data) {
        console.log('addEdits was called with params.data undefined');
    }
    if (params && params.data && params.data.changes && params.colDef && params.colDef.field) {
        var newList = JSON.parse(params.data.changes)
        newList.push(params.colDef.field)
        params.data.changes = JSON.stringify(newList)
    } else if (params && params.data && params.colDef && params.colDef.field) {
        params.data.changes = JSON.stringify([params.colDef.field])
    }
    if (params && params.data && params.colDef && params.colDef.field) {
        params.data[params.colDef.field] = params.newValue
    }
    return true;
}

dagfuncs.highlightEdits = function(params) {
    console.log(params); // Debugging line
    if (!params.data) {
        console.log('highlightEdits was called with params.data undefined');
    }
    if (params && params.data && params.data.changes && params.colDef && params.colDef.field) {
        if (JSON.parse(params.data.changes).includes(params.colDef.field))
            {return true}
    }
    return false;
}

```

I also removed getRowStyle which seemed to cause another issue when used concurrently:

```auto
getRowStyle = {
        "styleConditions": [
            {"condition": "params.data.make == 'Toyota'", "style": {"color": "blue"}}
        ]
    }

```

 ![new_example](https://us1.discourse-cdn.com/flex024/uploads/plot/original/3X/e/7/e7a74722752b0fb5ace19361025183c4e4be738f.png)

Thanks again for your help!

---

<div class="post-metadata">

**Author:** ![jinnyzor](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/jinnyzor/32/21194_2.png) [@jinnyzor](https://community.plotly.com/u/jinnyzor)\
**Post date:** [July 29, 2023, 10:18am UTC](https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266/5 "2023-07-29T10:18:17Z")

</div>

Yes, it fixed the row grouping in enterprise.

Alter the code to this and then test it.

```auto

dagfuncs.highlightEdits = function(params) {
    if (params.data) {
    If (params.data.changes) {
    if (JSON.parse(params.data.changes).includes(params.colDef.field))
        {return true}
    }
    return false;
}
}

```

---

<div class="post-metadata">

**Author:** ![kabure](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/kabure/32/19351_2.png) [@kabure](https://community.plotly.com/u/kabure)\
**Post date:** [April 25, 2024, 5:52pm UTC](https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266/6 "2024-04-25T17:52:49Z")

</div>

Hello guys, Thank you so much for this solution;

I’m trying to implement this in a project I’m building and it’s correctly highlighting the changed cell but if I undo the change it’s not removing the highlight  
 ![highlight-issue](https://us1.discourse-cdn.com/flex024/uploads/plot/original/3X/0/0/001ac7055a7d884143002cf5fb27354ec098da52.gif)  
  
  
There’s a way to control the highlight in a callback instead of the way it’s being done?

If you need/want, here I have a MRE you can use:

> [@How to correctly data type & input in Dash AG GRID? and other issues](https://community.plotly.com/t/how-to-correctly-data-type-input-in-dash-ag-grid-and-other-issues/84015/2):
>
> Hi @kabure You certainly picked a very advanced use-case for your first dash-ag-grid. sweat_smile Just so you know, this would be much easier with AG Grid Enterprise - it has a built-in pivot mode: If you don’t want to buy a license, it’s still possible to do this with the community version, it will just take a little more coding. For the truncated values, you have lots of options. The user can adjust the width by dragging the handle in the header. If you want the text to wrap see: [R…](https://dash.plotly.com/dash-ag-grid/row-height)

My goal is to undo the highlight if the user:  
1 - undo the change by setting the old value again ( I have a dcc.Store with the **original data** )  
2 - if the user submit the change, it should also remove the highligh as a new **original data** will be set

Did you have tried this approach or similar?

---

<div class="post-metadata">

**Author:** ![kabure](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/kabure/32/19351_2.png) [@kabure](https://community.plotly.com/u/kabure)\
**Post date:** [April 26, 2024, 5:37pm UTC](https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266/7 "2024-04-26T17:37:27Z")

</div>

Hey @jinnyzor I would like to report another interesting thing I discovered.

The issue happens when you try to have the highlight function and a “cell validator” function, as suggested in the following thread:

> [@Dash AG Grid: "undoRedoCellEditing" not working after changing rowData in callback](https://community.plotly.com/t/dash-ag-grid-undoredocellediting-not-working-after-changing-rowdata-in-callback/82027/10):
>
> Something like this example i have found? But this is javaskript dash, isn’t valueSetter: params =\> { validate(params.newValue, onSuccess, onFail); return false; }; validate = (newvalue, success, fail) =\> { if (isValid(newValue)) { success(); } else { fail(); } }; onSuccess = () =\> { // do transaction update to update the cell with the new value }; onFail = () =\> { // update some meta data property that highlights the cell signalling that the value has failed to valida…

The issue: If you use the function to validate the cell input, it will not highlight the changed cell anymore;

Do you know or have a hypothesis on why it happens?

---

<div class="post-metadata">

**Author:** ![AnnMarieW](https://avatars.discourse-cdn.com/v4/letter/a/ec9cab/32.png) [@AnnMarieW](https://community.plotly.com/u/AnnMarieW)\
**Post date:** [April 26, 2024, 5:51pm UTC](https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266/8 "2024-04-26T17:51:48Z")

</div>

Hi @kabure  
Two more sections of the docs to check:

Info on refreshing syles:

> **[Styling Cells | Dash for Python Documentation | Plotly](https://dash.plotly.com/dash-ag-grid/styling-cells#refresh-of-styles)**
>
> Styling cells with Dash AG Grid

How to use `gridApi.refreshCells()`

> **[Styling Cells | Dash for Python Documentation | Plotly](https://dash.plotly.com/dash-ag-grid/styling-cells#refresh-conditional-formatting-depending-on-other-columns)**
>
> Styling cells with Dash AG Grid

---

<div class="post-metadata">

**Author:** ![kabure](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/kabure/32/19351_2.png) [@kabure](https://community.plotly.com/u/kabure)\
**Post date:** [April 26, 2024, 6:03pm UTC](https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266/9 "2024-04-26T18:03:07Z")

</div>

Hey @AnnMarieW Thank you for the references again!

The biggest problem I’m facing is that I’m not too experienced in JS, so it’s a little painful to deal to the ag-grid things;

The ideal, for me, would be to highlight the cells by inserting/removing the values in a traditional callback based on the Col/Row pair; Reading the links you sent seems like it always need to be predefined or can be applied to a specific column 😪

I will spend some more time on this to try to understand the logic and then try to do some adaptations to my problem

---

<div class="post-metadata">

**Author:** ![AnnMarieW](https://avatars.discourse-cdn.com/v4/letter/a/ec9cab/32.png) [@AnnMarieW](https://community.plotly.com/u/AnnMarieW)\
**Post date:** [April 26, 2024, 6:10pm UTC](https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266/10 "2024-04-26T18:10:10Z")

</div>

Yes, the JavaScript allow you to do a lot of customization, but it’s challenging for most of us who mostly use Python.

Here’s one more reference you might find helpful in future projects – since you are familiar with DataTable - it’s an AG Grid version of all of the conditional formatting examples in the DataTable docs:

> **[Dash AG Grid Examples: Styling](https://dashaggridexamples.pythonanywhere.com/styling)**
>
> Tutorials, tips & tricks and additional examples to supplement the Dash AG Grid documentation.

---

<div class="post-metadata">

**Author:** ![kabure](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/kabure/32/19351_2.png) [@kabure](https://community.plotly.com/u/kabure)\
**Post date:** [April 28, 2024, 12:22am UTC](https://community.plotly.com/t/dash-ag-grid-single-cell-styling-after-cell-edit/77266/11 "2024-04-28T00:22:54Z")

</div>

Hello fellows! @jinnyzor @Cantelli @AnnMarieW

I would like to let you know that I found a way to set and remove the highlight in the cells using only Dash callback; It may not a too elegant solution, but it works very well

```auto
@app.callback(
    Output("ag-grid-table", "columnDefs"),
    Input("changed-cell", "data"),
    State("ag-grid-table", "columnDefs"),
)
def update_content_output(changed_cells, columns):
    key_to_check = "cellStyle"
    if changed_cells == []:
        to_change_cells = 0
        for (n, val) in enumerate(columns):
            if key_to_check in val.keys():
                if val['cellStyle']== {}:
                    pass
                else:
                    to_change_cells += 1
                    val["cellStyle"] = {}
            else:
                pass
        if to_change_cells == 0:
            return dash.no_update
        else:
            return columns
        
    list_of_sn = [val["val-ID"] for val in changed_cells]
    
    for (n, val) in enumerate(columns):
        if val["field"] in list_of_sn:
            sn_val=val["field"]
            list_of_rows = [
                int(get_rows["index_row"])
                for get_rows in changed_cells
                if get_rows["val-ID"] == sn_val
            ]
            cellStyle = {
                "styleConditions": [
                    {
                        "condition": f"[{','.join(map(str, list_of_rows))}].includes(params.node.childIndex)",
                        "style": {"backgroundColor": "yellow"},
                    },
                    {
                        "condition": f"![{','.join(map(str, list_of_rows))}].includes(params.node.childIndex)",
                        "style": {},
                    },
                ]
            }
            val["cellStyle"] = cellStyle
        else:
            if key_to_check in val.keys():
                if val["cellStyle"] != {}:
                    val["cellStyle"] = {}
                    # else:
                    # pass
                else:
                    pass
            else:
                pass
    return columns

```

If you have interest in understanding it better, you can access the full code here:

> **[GitHub - kaburelabs/dag-rows-styling: It's a implementation of style and...](https://github.com/kaburelabs/dag-rows-styling)**
>
> It's a implementation of style and other customizations for DASH-AG-GRID to the ROWS - kaburelabs/dag-rows-styling
