Filter in ag grid loses focus while typing

I am using ag grid and have filtering enabled. I have a button to clear all filters. This button is only enabled if there are any filters active. While I am typing in the filter, it loses focus. I did not have this problem with datatable. The problem also seems to go away without the Loading. dbc.spinner has the same problem as Loading. My min example was part of a much bigger more complex program. I have pared the program down to a simple example. When running the example, type an x in the filter for the first column and the filter will lose focus forcing a click in the filter to continue typing in the filter. The example almost always displays the issue for me. If it does not display the issue at first, delete what is in the filter and start typing again.

from dash import callback, Dash, dcc, html, Input, no_update, Output
import dash_ag_grid as dag
import dash_bootstrap_components as dbc
import pandas as pd

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP], suppress_callback_exceptions=True,
           meta_tags=[{'name': 'viewport', 'content': 'width=device-width, initial-scale=1.0'}])

dataframe = pd.DataFrame({'product_id': ['XYZ083-02ABC', 'NYZ082-02ABC', 'XYZ083-05ABC'],
                          'column_2_data': [2, 4, 6], 'column_3_data': [2, 4, 6], 'column_4_data': [2, 4, 6],
                          'column_5_data': [2, 4, 6], 'column_6_data': [2, 4, 6], 'column_7_data': [2, 4, 6],
                          'column_8_data': [2, 4, 6], 'column_9_data': [2, 4, 6], 'column_10_data': [2, 4, 6]})
default_col_def = {'filter': True,
                   'floatingFilter': True,
                   'resizable': True,
                   'sortable': True,
                   'editable': False,
                   'minWidth': 125,
                   'wrapHeaderText': True,
                   'autoHeaderHeight': True}

col_defs = [{'field': 'product_id', 'type' : None},
            {'field': 'column_2_data', 'type': 'numericColumn'},
            {'field': 'column_3_data', 'type': 'numericColumn'},
            {'field': 'column_4_data', 'type': 'numericColumn'},
            {'field': 'column_5_data', 'type': 'numericColumn'},
            {'field': 'column_6_data', 'type': 'numericColumn'},
            {'field': 'column_7_data', 'type': 'numericColumn'},
            {'field': 'column_8_data', 'type': 'numericColumn'},
            {'field': 'column_9_data', 'type': 'numericColumn'},
            {'field': 'column_10_data', 'type': 'numericColumn'}]

datatable = dag.AgGrid(id="datatable",
                       columnDefs=col_defs,
                       defaultColDef=default_col_def,
                       rowData=dataframe.to_dict('records'),
                       dashGridOptions={'undoRedoCellEditing': True},
                       suppressDragLeaveHidesColumns=False,
                       persistence=True,
                       style={'resize': 'vertical', 'overflow': 'hidden'})

page = dcc.Loading(children=[dbc.Row(dbc.Col(html.Button(id="clear", n_clicks=0, children='Clear Filter', disabled=True),
                                             )),
                             dbc.ListGroup(children=[dbc.ListGroupItem(datatable)], flush=True)])

app.layout = dbc.Container(dbc.Card(dbc.CardBody(page)),
                           fluid=True)

@callback(Output("datatable", 'filterModel'),
          Input("clear", 'n_clicks'),
          prevent_initial_call=True)
def clear_filter(n_clicks):
    """Clear the filter query."""
    return {} if n_clicks else no_update

@callback(Output("clear", 'disabled'),
          Input("datatable", 'filterModel'),
          prevent_initial_call=True)
def enable_clear_filter(filter_query):
    """Enable/disable the clear filter button."""
    return filter_query == {}

if __name__ == '__main__':
    app.run_server(port=8850, debug=True, threaded=False, use_reloader=False)

Hello @Brent,

This isnt an issue with the Grid, but an issue with the dcc.Loading.

The filter will update the model as you type, this issue would flash the loading screen each time you toggled a sort, if the grid was large enough. And would also be an issue for infinite scroll.

If you want to display a loading screen while a page is loading, you should check out my article here:

Having this level of flicker can indeed cause issues with an interface…

If you want to display a loading overlay while the data is loading to the grid, you can use the default loadingOverlay.

Just dont pass rowData to the grid when it is first loading:

from dash import callback, Dash, dcc, html, Input, no_update, Output
import dash_ag_grid as dag
import dash_bootstrap_components as dbc
import pandas as pd

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP], suppress_callback_exceptions=True,
           meta_tags=[{'name': 'viewport', 'content': 'width=device-width, initial-scale=1.0'}])

dataframe = pd.DataFrame({'product_id': ['XYZ083-02ABC', 'NYZ082-02ABC', 'XYZ083-05ABC'],
                          'column_2_data': [2, 4, 6], 'column_3_data': [2, 4, 6], 'column_4_data': [2, 4, 6],
                          'column_5_data': [2, 4, 6], 'column_6_data': [2, 4, 6], 'column_7_data': [2, 4, 6],
                          'column_8_data': [2, 4, 6], 'column_9_data': [2, 4, 6], 'column_10_data': [2, 4, 6]})
default_col_def = {'filter': True,
                   'floatingFilter': True,
                   'resizable': True,
                   'sortable': True,
                   'editable': False,
                   'minWidth': 125,
                   'wrapHeaderText': True,
                   'autoHeaderHeight': True}

col_defs = [{'field': 'product_id', 'type' : None},
            {'field': 'column_2_data', 'type': 'numericColumn'},
            {'field': 'column_3_data', 'type': 'numericColumn'},
            {'field': 'column_4_data', 'type': 'numericColumn'},
            {'field': 'column_5_data', 'type': 'numericColumn'},
            {'field': 'column_6_data', 'type': 'numericColumn'},
            {'field': 'column_7_data', 'type': 'numericColumn'},
            {'field': 'column_8_data', 'type': 'numericColumn'},
            {'field': 'column_9_data', 'type': 'numericColumn'},
            {'field': 'column_10_data', 'type': 'numericColumn'}]

datatable = dag.AgGrid(id="datatable",
                       columnDefs=col_defs,
                       defaultColDef=default_col_def,
                       # rowData=dataframe.to_dict('records'),
                       dashGridOptions={'undoRedoCellEditing': True},
                       suppressDragLeaveHidesColumns=False,
                       persistence=True,
                       style={'resize': 'vertical', 'overflow': 'hidden'})

page = dcc.Loading(children=[dbc.Row(dbc.Col(html.Button(id="clear", n_clicks=0, children='Clear Filter', disabled=True),
                                             )),
                             dbc.ListGroup(children=[dbc.ListGroupItem(datatable)], flush=True)])

app.layout = dbc.Container(dbc.Card(dbc.CardBody(page)),
                           fluid=True)

@callback(Output("datatable", 'filterModel'),
          Input("clear", 'n_clicks'),
          prevent_initial_call=True)
def clear_filter(n_clicks):
    """Clear the filter query."""
    return {} if n_clicks else no_update

@callback(Output("clear", 'disabled'),
          Input("datatable", 'filterModel'),
          prevent_initial_call=True)
def enable_clear_filter(filter_query):
    """Enable/disable the clear filter button."""
    return filter_query == {}

if __name__ == '__main__':
    app.run_server(port=8850, debug=True)

You could even use some css magic with my loading screen trick from the topic and use a :has(.ag-overlay-loading-center to trigger your loading.

Here is an example with a 2 second fuse on the loading of the data:

from dash import callback, Dash, dcc, html, Input, no_update, Output
import dash_ag_grid as dag
import dash_bootstrap_components as dbc
import pandas as pd

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP], suppress_callback_exceptions=True,
           meta_tags=[{'name': 'viewport', 'content': 'width=device-width, initial-scale=1.0'}])

dataframe = pd.DataFrame({'product_id': ['XYZ083-02ABC', 'NYZ082-02ABC', 'XYZ083-05ABC'],
                          'column_2_data': [2, 4, 6], 'column_3_data': [2, 4, 6], 'column_4_data': [2, 4, 6],
                          'column_5_data': [2, 4, 6], 'column_6_data': [2, 4, 6], 'column_7_data': [2, 4, 6],
                          'column_8_data': [2, 4, 6], 'column_9_data': [2, 4, 6], 'column_10_data': [2, 4, 6]})
default_col_def = {'filter': True,
                   'floatingFilter': True,
                   'resizable': True,
                   'sortable': True,
                   'editable': False,
                   'minWidth': 125,
                   'wrapHeaderText': True,
                   'autoHeaderHeight': True}

col_defs = [{'field': 'product_id', 'type' : None},
            {'field': 'column_2_data', 'type': 'numericColumn'},
            {'field': 'column_3_data', 'type': 'numericColumn'},
            {'field': 'column_4_data', 'type': 'numericColumn'},
            {'field': 'column_5_data', 'type': 'numericColumn'},
            {'field': 'column_6_data', 'type': 'numericColumn'},
            {'field': 'column_7_data', 'type': 'numericColumn'},
            {'field': 'column_8_data', 'type': 'numericColumn'},
            {'field': 'column_9_data', 'type': 'numericColumn'},
            {'field': 'column_10_data', 'type': 'numericColumn'}]

datatable = dag.AgGrid(id="datatable",
                       columnDefs=col_defs,
                       defaultColDef=default_col_def,
                       # rowData=dataframe.to_dict('records'),
                       dashGridOptions={'undoRedoCellEditing': True},
                       suppressDragLeaveHidesColumns=False,
                       persistence=True,
                       style={'resize': 'vertical', 'overflow': 'hidden'})

page = [dbc.Row(dbc.Col(html.Button(id="clear", n_clicks=0, children='Clear Filter', disabled=True),
                                             )),
                             dbc.ListGroup(children=[dbc.ListGroupItem(datatable)], flush=True)]

app.layout = dbc.Container(dbc.Card(dbc.CardBody(page)),
                           fluid=True)

@callback(Output("datatable", 'filterModel'),
          Input("clear", 'n_clicks'),
          prevent_initial_call=True)
def clear_filter(n_clicks):
    """Clear the filter query."""
    return {} if n_clicks else no_update

@callback(Output("clear", 'disabled'),
          Input("datatable", 'filterModel'),
          prevent_initial_call=True)
def enable_clear_filter(filter_query):
    """Enable/disable the clear filter button."""
    return filter_query == {}

@callback(Output('datatable', 'rowData'),
          Input('datatable','id'))
def firstLoad(id):
    import time
    time.sleep(2) # mimic long load
    return dataframe.to_dict('records')

if __name__ == '__main__':
    app.run_server(port=8850, debug=True)

To demonstrate this issue even further, try to maintain focus on this input:

from dash import callback, Dash, dcc, html, Input, Output
app = Dash(__name__)

app.layout = dcc.Loading([dcc.Input(id='testing', debounce=False), html.Div(id='output')])

@callback(Output('output', 'children'), Input('testing', 'value'))
def showValue(v):
    return v

if __name__ == '__main__':
    app.run_server(port=8850, debug=True)
1 Like

Another solution is to add an apply button to the filter. The annoying flash on every keystroke goes away. However, this works best if you don’t use the floating filters. More info in the dash-ag-grid docs

default_col_def = {'filter': True,
                   "filterParams": {
                       "buttons": ["reset", "apply"],
                   },
                   #'floatingFilter': True,
                   'resizable': True,
                   'sortable': True,
                   'editable': False,
                   'minWidth': 125,
                   'wrapHeaderText': True,
                   'autoHeaderHeight': True}

1 Like

When I run the last example, It just keeps bouncing between showing Loading… in the top left corner and an empty grid with just the headers with a loading box inside of the grid. The data actually never loads.

If I comment out the sleep, the firstload callback keeps triggering and the data does get loaded over and over.

I’m not sure what to do with the loading screen trick you mentioned. I copied the example in your article. When I run it, it bounces back and forth between showing Loading… in the top left corner and the animated hash in the middle of the page repeatedly. I’m not sure if that is what it is supposed to do.

Are you using jupyter? Is that why you always have your extra stuff in the app run?

No. I am running inside of emacs in debug mode.

Ok, well, add your additional statements, as it sounds like the hot_reloading is an issue for you. :slight_smile:

Okay, I got it to work. It does not like debug=True.

Idk, I run it on Pycharm, so how I gave it is how it works. XD