Dash AG Grid "deleteSelectedRows" as "rowData" or "virtualRowData"

Hi everyone!

I have a callback where I am adding or deleting rows. After adding/deleting, I would like to pass the table as rowData or virtualRowData to another callback.
Dash is able to do that if row is added, but not able to do that if row is deleted.

I believe, this line is causing the problem. As I understand, table with deleted rows is not saved in/as virtualRowData, correct? How do I bypass it? How do I pass the data/table with a deleted row to another callback ? How do I push the table with a deleted row to rowData or virtualRowData?

Output("sql-table", "deleteSelectedRows")

Here is the full code:

@callback(
    [
        Output("sql-table", "deleteSelectedRows"), #<====problem?
        Output("sql-table", "rowData"),
    ],
    [
        Input("delete-row-button", "n_clicks"),
        Input("add-row-button", "n_clicks")
    ],
    State("sql-table", "rowData"),
    prevent_initial_call=True,
)

def update_dash_table(n_dlt, n_add, data):
    if ctx.triggered_id == "add-row-button":
        #do something
        return False, updated_table.to_dict("records")

    elif ctx.triggered_id == "delete-row-button":
        return True, no_update

Thanks for help in advance!

Hello @mrel,

What version are you running?

Also, how are you triggering a callback to use this new rowData or virtualRowData?

Hi @jinnyzor ,

I am using Dash version “2.11.1”.

So when I click the “Add row” button, the callback in my post is triggered, a row is added and the new table is saved in Output("sql-table", "rowData").
Then, in another callback #2, I input that same table (with added row) as State('sql-table', 'virtualRowData') and process it further. It works.

When I click the “Delete row” buton, the callback in my post is triggered, a row is deleted (I see it on my screen). But the data that is appearing in the other callback #2 is still the original data (row is not deleted). It seems like the new table (after deleted a row) is not saved in/parsed as virtualRowData.
So even though I see with my own eyes on my screen, that the row is deleted, I cannot pass this new table (after deleting a row) to another callback #2 as State('sql-table', 'virtualRowData').

I don’t know how to explain it, but is there no “connection” between deleteSelectedRows and virtualRowData ?

AG Grid.

What is the trigger for the secondary grid / table?

Apologies. AG Grid version is 2.3.0
image

Trigger for secondary callback is simply a “Save to Excel” or “Save to SQL” button (I have both and both are supposed to be taking data from the main callback for adding/deleting rows).

@callback(
    [
        Output('placeholder', 'children'),
        Output("store", "data")
    ],
    [
        Input('save-to-sql', 'n_clicks'),
        Input("interval", "n_intervals")
    ],
    [
        State('sql-table', 'virtualRowData'), #<====here is where the data table (after adding/deleting rows should be falling into
        State('original-data', 'data'),
        State('store', 'data')
    ],
    prevent_initial_call=True
)

Could you please provide a full MRE? Id like to run on my machine and see what is going on. And see if there is a better way to do this.

I think I found a clue and I think I need to rephrase my question.

Question: How do I find deleted rows? How do I save them in a separate df?

I did some research and found this topic where they are referencing to a callback parameter data_orevious. I didn’t know about it.

So I can find deleted rows by comparing “current data” to “previous data”. But how do I do that in AG Grid? The question topic above is about a DataTable case, not AG Grid.

Please tell me if I should make a new topic with a proper question (and close this one) or we can continue here.

Ok.

You can’t save deletedRows into a different row, but what you can do is insert a spot where when you click the delete button, you also store the selectedRows into a new df.

Should it look something like this?
-added State("sql-table", "selectedRows")
-added Output("deleted-rows", "rowData")
-added df_deleted=pd.DataFrame(selection)

@callback(
    [
        Output("sql-table", "deleteSelectedRows"),
        Output("sql-table", "rowData"),  # <=== new line 
        Output("deleted-rows", "rowData")
    ],
    [
        Input("delete-row-button", "n_clicks"),
        Input("add-row-button", "n_clicks")
    ],
    [
        State("sql-table", "rowData"),
        State("sql-table", "selectedRows"),   # <=== new line 
    ],
    prevent_initial_call=True,
)

def update_dash_table(n_dlt, n_add, data, selection):
    if ctx.triggered_id == "add-row-button":
        #do something
        return False, updated_table.to_dict("records"), no_update

    elif ctx.triggered_id == "delete-row-button":
        df_deleted=pd.DataFrame(selection)  # <=== new line 
        return True, no_update, df_deleted

I’d honestly do something like this using rowTransaction:

@callback(
    [
        Output("sql-table", "rowTransaction"),
        Output("deleted-rows", "rowTransaction")
    ],
    [
        Input("delete-row-button", "n_clicks"),
        Input("add-row-button", "n_clicks")
    ],
    [
        State("sql-table", "selectedRows"), 
    ],
    prevent_initial_call=True,
)

def update_dash_table(n_dlt, n_add, selection):
    if ctx.triggered_id == "add-row-button":
        #do something
        return {'add': new_records}, no_update

    elif ctx.triggered_id == "delete-row-button":
        return {'delete': selection}, {'add': selection}

At first I couldn’t make it work. Not sure if the problem was in rowTransaction or in panda dataframes or type of data.

Then I combined my code + your code + this documentation and I made it work.

Thanks a lot for all your help!

Here is the full code:

@callback(
        Output("sql-table", "rowData"),
        Output("sql-table", "deleteSelectedRows"),
        Output("selected-data", "rowData"),
        Input("delete-row-button", "n_clicks"),
        Input("add-row-button", "n_clicks"),
        State("sql-table", "rowData"),
        State("sql-table", "selectedRows"),
        prevent_initial_call = True
)

def update_dash_table(n_dlt, n_add, data, selection):
    if ctx.triggered_id == "add-row-button":
        #do something
        return updated_table.to_dict("records"), no_update, no_update

    elif ctx.triggered_id == "delete-row-button":
        if selection is None:
            return no_update, no_update, no_update
        return no_update, selection, selection

Hmm, in your code, all your deleted rows would get blasted by the next deleted rows that you were giving. I’d recommend using rowTransaction for your deleted grid.

rowTransaction updates the info clientside, so it is interesting that you werent able to get it working.

Ok, I see what you mean.

I think in my case it could be fine for now. After I am adding/deleting rows, I will be saving the data in SQL (or Excel) via a separate callback/button. And then I am pulling the newly saved edited data back again.
So each time I should have freshly saved data , thus, I can add/delete rows anew again without worrying about losing previously added/deleted ones.

Hmm, ok, just keep it in mind. :slight_smile:

That’s also a bunch of back and forth between the server. :wink:

Yes, you are absolutely right about back and forth between the server.
It’s my first attempt to build a fully functioning and complex webApp, so just learning by doing :slight_smile:

I will try to fix the back and forth thing in future updates (I need to at least make it work first :sweat_smile:)

Thanks again for your time!

1 Like