✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
⚡️ Concerned about the grid? Kyle Baranko teaches how to predicting peak loads using XGBoost. Register for the August webinar!

Mixing selectable rows with backend page/sort/filter

I’m trying to implement a table with selectable row and backend paging, sorting, and filtering. I see others have had similar problems as I am having [1], [2]. I find that with a modest sized table (300 rows x 30 cols) selectable rows are unusable due to a delay of about 2 seconds between clicking the selection and seeing the checkmark. When I do backend paging the selection is snappy, but the selected rows disappear when changing pages, and the callback is invoked in unpredictable ways (sometimes the selected_row_ids are cleared, other times not).

What I want to achieve is that selected rows always remain visible even when they would otherwise be paged, sorted, or filtered away. Does anyone have an example, or suggestions of how to make one of the examples in the documentation accomplish this? Thanks,

Matt

[1] Keep selected rows after sorting/filtering in data table
[2] DataTable Selected Row IDs with Backend Paging

I decided to take a different approach and I’m posting it here in case someone else finds it useful. I created a separate table that stores the selected row of the main table. The main table can then use backend paging, sorting, and filtering retaining it’s responsiveness, and the table of selected row can ignore the filters and sorting of the main table. It works well for my application.

import dash
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_table
import pandas as pd


df = pd.DataFrame({'id': [0,1,2,3,4,5,6,7,8], 'A': ['a','b','c','d','e','f','g','h','i'], 'B': [11,12,13,14,5,16,17,18,19]})

app = dash.Dash(__name__)

PAGE_SIZE = 3

app.layout = html.Div([dash_table.DataTable(
                            id='table-paging-and-sorting',
                            columns=[{'name': i, 'id': i} for i in sorted(df.columns) if i != 'id'],
                            page_current=0,
                            page_size=PAGE_SIZE,
                            page_action='custom',
                            sort_action='custom',
                            sort_mode='single',
                            sort_by=[],
                            row_selectable = 'multi',
                            selected_rows = []
                          ),
                      html.H1('Selected'),
                      dash_table.DataTable(
                            id='table-selected',
                            columns=[{'name': i, 'id': i} for i in sorted(df.columns) if i != 'id'],
                            page_action='native',
                            sort_action='native',
                            sort_mode='single',
                            row_deletable = True,
                            sort_by=[],
                          )],
                      style = {'width': '20%'})

@app.callback(
    Output('table-paging-and-sorting', 'data'),
    [Input('table-paging-and-sorting', "page_current"),
     Input('table-paging-and-sorting', 'sort_by')],
    [State('table-paging-and-sorting', "page_size")])
def update_table(page_current, sort_by, page_size):
    app.logger.info(f"update_table page_current:{page_current} sort_by:{sort_by} page_size:{page_size}")
    if len(sort_by):
        dff = df.sort_values(sort_by[0]['column_id'], ascending=sort_by[0]['direction'] == 'asc', inplace=False)
    else:
        dff = df
    return dff.iloc[page_current*page_size:(page_current+ 1)*page_size].to_dict('records')

@app.callback(
    Output('table-selected', 'data'),
    [Input('table-paging-and-sorting', 'selected_row_ids')],
    [State('table-selected', 'data')])
def update_selection(selected_row_ids, data_current):
    app.logger.info(f"update_selection selected_row_ids:{selected_row_ids} data_current:{data_current}")
    if selected_row_ids is not None and len(selected_row_ids) > 0:
        data_new = df[df.id.isin(selected_row_ids)].to_dict('records')
        if data_current is not None:
            ids = [r['id'] for r in data_current]
            for r in data_new:
                if r['id'] not in ids:
                    data_current.append(r)
        else:
            data_current = data_new
    return data_current
    

if __name__ == '__main__':
    app.run_server(debug=True, host = '0.0.0.0')