Having a DataTable on my app, it operates as expected. I import the selected_cells field from the datatable into the callback and it is initially None because nothing has been clicked on yet.
Then I click on a cell and the print statement i have set up shows me the correct values of what i clicked on.
However I am running into an issue with reverting the selection back to None so that the cell de-selects.
Can anyone help with how to get that None value back to the DataTable from the callback?
selected_cells ( dict ; optional): selected_cells represents the set of cells that are selected, as an array of objects, each item similar to active_cell . Multiple cells can be selected by holding down shift and clicking on a different cell or holding down shift and navigating with the arrow keys. selected_cells has the following type: list of dicts containing keys ârowâ, âcolumnâ, ârow_idâ, âcolumn_idâ. Those keys have the following types:
Hi! In case someone arrives here looking for a solution to deselect cells if you have more than one table and the user clicks on another table, I have updated @AnnMarieW 's (great) solution with a pattern-matching callback:
import dash
from dash import html, dash_table, Input, Output, State, ALL, ctx
import plotly.express as px
import json
# ctx only works for dash >= 2.4.0
# from dash import callback_context as ctx # run this if you have dash<2.4
df = px.data.iris()
def table(index, data=df) :
data = data.iloc[10*index:10*(index+1)]
return html.Div([
dash_table.DataTable(
id={'type':'table', 'index':index},
columns=[{"name": i, "id": i} for i in data.columns],
data=data.to_dict("records"),
),
html.Br()
])
app = dash.Dash(__name__)
app.layout = html.Div(
[table(i) for i in range(3)],
style={'padding':'30px'})
@app.callback(
Output({'type':'table', 'index':ALL}, 'selected_cells'),
Output({'type':'table', 'index':ALL}, 'active_cell'),
Input({'type':'table', 'index':ALL}, 'selected_cells'),
State({'type':'table', 'index':ALL}, 'id'),
prevent_initial_call=True
)
def unselect_cells(cells, tables):
table_ids = [t['index'] for t in tables]
trigger = ctx.triggered_id.index # only works if you have dash>=2.4.0
#trigger = json.loads(ctx.triggered[0]['prop_id'].split('.')[0])['index'] # run this if you have dash<2.4.0
new_selection = [[] if t!=trigger else c for c, t in zip(cells, table_ids)]
new_active = [None if s==[] else s[0] for s in new_selection]
return new_selection, new_active
if __name__ == "__main__":
app.run_server(debug=True)
As Ann Marie noted, It is necessary that both selected_cells and active_cell are updated. They have a slightly different structure: selected_cells is a dictionary with the row information of each selected cell (as there can be more than one). active_cell is just a dict with the row information. As we are using pattern-matching selector ALL, each input/state (cells, tables) is a LIST of the value of those arguments in each of the tables.