Dash AG Grid: "undoRedoCellEditing" not working after changing rowData in callback

Hello everybody,

i am facing an issue with the property “undoRedoCellEditing”. Without a callback that changes the content of “rowData” i am able to undo/redo the edited cell (Key CTRL-Z). As soon as this callback is activated this property (Key CTRL-Z) has no effect

Thanks in Advance. I would appreciate any help. I’ve just switched from dash.dataTable to AgGrid.

AgGrid definition

grid = dag.AgGrid(
    id="portfolio-grid",
    className="ag-theme-alpine-dark",
    columnDefs=columnDefs,
    rowData=df.to_dict("records"),
    columnSize="sizeToFit",
    defaultColDef=defaultColDef,
    dashGridOptions={"undoRedoCellEditing": True, 'rowSelection': "multiple", 'rowDragManaged': True,  'domLayout': 'autoHeight'},
    style={"height": "100%", "width": "100%"},
)

callback: I have even tried to return the dashGridOptions. But it showed no effect.
This callback is just to control the type/format of the cells. Probably there is a clever and simpler way to perform it…

@app.callback(
    Output("portfolio-grid", "rowData", allow_duplicate=True),
    Output("portfolio-grid", "dashGridOptions"),
    Input("portfolio-grid", "cellValueChanged"),
    State("portfolio-grid", "rowData"),
    State("portfolio-grid", "dashGridOptions"), prevent_initial_call=True
)
def update(cell_changed, rowData, dashGridOptions):
    if cell_changed is not None:
        row_index = cell_changed['rowIndex']
        col_id = cell_changed['colId']
        old_value = cell_changed['oldValue']
        new_value = cell_changed['value']

        if col_id in ['Column_A', 'Column_B', 'Column_C', 'Column_D']:
            # if new_value.lstrip('-').isdigit():
            try:
                if float(new_value):
                    rowData[row_index][col_id] = new_value
            except ValueError:
                if col_id in ['Column_E', 'Column_F'] and new_value == '':
                    rowData[row_index][col_id] = new_value
                else:
                    rowData[row_index][col_id] = old_value

        dashGridOptions["undoRedoCellEditing"] = True
        return rowData, dashGridOptions
    else:
        return dash.no_update

Hello @Peete77,

Welcome to the community!

You do not need a callback to perform this action:

1 - v31of dash ag grid (released yesterday) has inferred data types, so if the data looks like a number, it will automatically adjust your data to conform to being a number
2 - you can pass your own valueSetter similarly to how you are doing this above. This is JS so it can be a little trickier.

1 Like

Here is a basic example of how you could implement this in v31:

import dash_ag_grid as dag
from dash import *

app = Dash(__name__)

rowData = [{f"{x}": x + .02 for x in range(4)} for y in range(4)]
columnDefs = [{'field': f"{x}"} for x in range(4)]

app.layout = dag.AgGrid(rowData=rowData, columnDefs=columnDefs, defaultColDef={'editable': True, 'cellEditorParams': {'step': '.01', 'showStepperButtons': True}})

app.run(debug=True)

Thank you very much for your quick reply.
I try to decipher it :smile:

But anyway. Why undo redo cell change is only working if i comment this callback out?

Thanks in advance!

When you update the rowData, you are destroying the history. Therefore there is nothing to recall.

You might be able to alter to a rowTransaction, but there is already a mechanism that you are trying to implement by the grid. This is what I mentioned above. :grin:

Thank you. I thought I was affecting undo redo by changing rowdata.

I’ve tried previously with valueFormater but probably i missed the point.

“valueFormatter”: {“function”: “d3.format(‘,.2f’)(params.value)”},

My intention was to allow only numbers (floats as well as negative ones)to be inserted to certain columns. If the users try to type a text the old value should be set again. If users empty the cell the value should be np.nan. if dash.datatable i could manage it without a callback. With my callback here it worked but I’ve loosen ctrl-z functionality

I’ll try to figure out your basic example.

In v31, if the grid detects your column is a number, the input itself won’t let you type anything except for things relating ti a valid number.

So, you can’t type anything else, which means you don’t need a callback or valeSetter to make sure the values are acceptable.

Heĺlo jinnyzor,

Thank you again for supporting me to learn how to use the new version of AgGrid. I’ve tried your basic example. If i type anything else than a number it cleans up the cell. If i want to have the previous value i just have to use the short cut ctrl-z. :+1:

Can i automatically fill the cell with the previous value without using a callback? Can i also set the value to np.nan if the content was deleted without a callback?

Thank you very much!

You can use the valueSetter to see if there is a null value, and if null, then return false.

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 validate
};

Here is my example with testing for a null value and if null it will return the old value:

dashAgGridFunctions.js in assets folder

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

dagfuncs.testValue = (params) => {
    if (!params.newValue) {
        return false
    }
    params.data[params.column.colId] = params.newValue
    console.log(params.newValue)
    return true
}

app.py

import dash_ag_grid as dag
from dash import *

app = Dash(__name__)

rowData = [{f"{x}": x + .02 for x in range(4)} for y in range(4)]
columnDefs = [{'field': f"{x}"} for x in range(4)]

app.layout = dag.AgGrid(rowData=rowData, columnDefs=columnDefs,
                        defaultColDef={'editable': True,
                                       'cellEditorParams': {'step': '.01', 'showStepperButtons': True},
                                       'valueSetter': {'function': 'testValue(params)'}})

app.run(debug=True)
2 Likes

Thank you very much.
I’ll try it.

1 Like

Thank you very much!

I am going to try it.
I’ve just have no experience with javascript so far.

@Peete77 You might find this post helpful:

Thank you!