Dash AG Grid Event Listeners (v 31.2)

Good day, All,

Here are a few examples of the new things that you can do with the latest release of Dash AG Grid 31.2+.

eventListeners:

These can be passed as such:

eventListeners={'cellFocused': ['myfunction1(params, setGridProps)']}

Where eventListeners is a dictionary where the key is the eventListener that you want to listen to, these can be found here. The value of the key is an array of functions that you want to add to the specific event, this is helpful in the event that you want two separate things to happen when a user interacts with the grid.

These eventListeners are added upon the grid’s event, gridReady. This means that events will only apply once the grid is ready for them, this is a big change from the previous way that eventListeners had to be added:

app.clientside_callback(
    """(id) => {
        dash_ag_grid.getApiAsync(id).then((grid) => {
            grid.addEventListener('cellFocused',
                (params) => {
                    myFunction1(params)
                }
            )
        })
        return window.dash_clientside.no_update
    }""",
    Output('grid', 'id'),
    Input('grid', 'id')
)

why this matters?

The above code will work as long as the grid is rendered and ready within 2 minutes of the callback being triggered, however, if you dont have the grid rendered in time, you will receive this in the browser console:

With the new method, you no longer need to wait for the grid to be rendered as these will automatically be loaded when ready. These are important for things like if the grid is loaded into a component that doesnt exist in the DOM tree immediately. (Modals, popovers, offcanvas, drawers, and tabs (depending on configuration))

Example Old
import dash_ag_grid as dag
from dash import Dash, html, dcc, Input, Output, State, no_update, Patch
import pandas as pd
import json
import dash_mantine_components as dmc

snippet = dmc.Prism(language="Python",children=
    """import dash_ag_grid as dag
from dash import Dash, html, dcc, Input, Output, State, no_update, Patch
import pandas as pd
import json

app = Dash(__name__)


df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)


columnDefs = [
    {"field": "athlete"},
    {"field": "age", "filter": "agNumberColumnFilter", "maxWidth": 100},
    {"field": "country"},
    {
        "headerName": "Date",
        "filter": "agDateColumnFilter",
        "valueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"},
        "valueFormatter": {"function": "params.data.date"},
    },
    {"field": "sport"},
    {"field": "total"},
]

app.layout = html.Div(
    [
        dmc.Modal([
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            rowData=df.to_dict("records"),
            columnSize="sizeToFit",
            dashGridOptions={"animateRows": False, 'rowSelection': 'single'},
        ),
        html.Div(id='info'),
        ],
        id='modalTest',
        fullScreen=True),
        html.Button('Open Modal', id='openModal')
    ]
)

app.clientside_callback(
    """"""(id) => {
        dash_ag_grid.getApiAsync(id).then((grid) => {
            grid.addEventListener('cellFocused',
                (params) => {
                    params.api.setNodesSelected({nodes: [params.api.getRowNode(params.rowIndex)], newValue: true})
                }
            )
        })
        return window.dash_clientside.no_update
    }"""""",
    Output('grid', 'id'),
    Input('grid', 'id')
)

@app.callback(
    Output('info', 'children'),
    Input('grid', 'selectedRows')
)
def showFilters(d):
    if d:
        return json.dumps(d)
    return no_update

@app.callback(
    Output('modalTest', 'opened'),
    Input('openModal', 'n_clicks'),
    prevent_initial_call=True
)
def openModal(n):
    if n:
        return True
    return no_update


if __name__ == "__main__":
    app.run(debug=True, port=1234)"""
)

app = Dash(__name__, title="Modal Old", update_title=False)


df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)


columnDefs = [
    {"field": "athlete"},
    {"field": "age", "filter": "agNumberColumnFilter", "maxWidth": 100},
    {"field": "country"},
    {
        "headerName": "Date",
        "filter": "agDateColumnFilter",
        "valueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"},
        "valueFormatter": {"function": "params.data.date"},
    },
    {"field": "sport"},
    {"field": "total"},
]

app.layout = html.Div(
    [
        dmc.Modal([
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            rowData=df.to_dict("records"),
            columnSize="sizeToFit",
            dashGridOptions={"animateRows": False, 'rowSelection': 'single'},
        ),
        html.Div(id='info'),
        ],
        id='modalTest',
        fullScreen=True),
        html.Button('Open Modal', id='openModal'),
        snippet
    ]
)

app.clientside_callback(
    """(id) => {
        dash_ag_grid.getApiAsync(id).then((grid) => {
            grid.addEventListener('cellFocused',
                (params) => {
                    params.api.setNodesSelected({nodes: [params.api.getRowNode(params.rowIndex)], newValue: true})
                }
            )
        })
        return window.dash_clientside.no_update
    }""",
    Output('grid', 'id'),
    Input('grid', 'id')
)

@app.callback(
    Output('info', 'children'),
    Input('grid', 'selectedRows')
)
def showFilters(d):
    if d:
        return json.dumps(d)
    return no_update

@app.callback(
    Output('modalTest', 'opened'),
    Input('openModal', 'n_clicks'),
    prevent_initial_call=True
)
def openModal(n):
    if n:
        return True
    return no_update


if __name__ == "__main__":
    app.run(debug=True, port=1234)
Example New
import dash_ag_grid as dag
from dash import Dash, html, dcc, Input, Output, State, no_update, Patch
import pandas as pd
import json
import dash_mantine_components as dmc

snippet = dmc.Prism(language="Python",children=
    """import dash_ag_grid as dag
from dash import Dash, html, dcc, Input, Output, State, no_update, Patch
import pandas as pd
import json

app = Dash(__name__)


df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)


columnDefs = [
    {"field": "athlete"},
    {"field": "age", "filter": "agNumberColumnFilter", "maxWidth": 100},
    {"field": "country"},
    {
        "headerName": "Date",
        "filter": "agDateColumnFilter",
        "valueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"},
        "valueFormatter": {"function": "params.data.date"},
    },
    {"field": "sport"},
    {"field": "total"},
]


app.layout = html.Div(
    [
        dmc.Modal([
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            rowData=df.to_dict("records"),
            columnSize="sizeToFit",
            dashGridOptions={"animateRows": False, 'rowSelection': 'single'},
            eventListeners={'cellFocused': ["params.api.setNodesSelected({nodes: [params.api.getRowNode(params.rowIndex)], newValue: true})"]}
        ),
        html.Div(id='info'),
        ],
        id='modalTest',
        fullScreen=True),
        html.Button('Open Modal', id='openModal')
    ]
)

@app.callback(
    Output('info', 'children'),
    Input('grid', 'selectedRows')
)
def showFilters(d):
    if d:
        return json.dumps(d)
    return no_update

@app.callback(
    Output('modalTest', 'opened'),
    Input('openModal', 'n_clicks'),
    prevent_initial_call=True
)
def openModal(n):
    if n:
        return True
    return no_update


if __name__ == "__main__":
    app.run(debug=True, port=1233)"""
)

app = Dash(__name__, title="Modal New", update_title=False)


df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)


columnDefs = [
    {"field": "athlete"},
    {"field": "age", "filter": "agNumberColumnFilter", "maxWidth": 100},
    {"field": "country"},
    {
        "headerName": "Date",
        "filter": "agDateColumnFilter",
        "valueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"},
        "valueFormatter": {"function": "params.data.date"},
    },
    {"field": "sport"},
    {"field": "total"},
]

app.layout = html.Div(
    [
        dmc.Modal([
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            rowData=df.to_dict("records"),
            columnSize="sizeToFit",
            dashGridOptions={"animateRows": False, 'rowSelection': 'single'},
            eventListeners={'cellFocused': ["params.api.setNodesSelected({nodes: [params.api.getRowNode(params.rowIndex)], newValue: true})"]}
        ),
        html.Div(id='info'),
        ],
        id='modalTest',
        fullScreen=True),
        html.Button('Open Modal', id='openModal'),
        snippet
    ]
)

@app.callback(
    Output('info', 'children'),
    Input('grid', 'selectedRows')
)
def showFilters(d):
    if d:
        return json.dumps(d)
    return no_update

@app.callback(
    Output('modalTest', 'opened'),
    Input('openModal', 'n_clicks'),
    prevent_initial_call=True
)
def openModal(n):
    if n:
        return True
    return no_update


if __name__ == "__main__":
    app.run(debug=True, port=1233)

Event Listeners brings access to setGridProps

When adding an event listener via the new method, you are given access to set props directly from the function without needing to use set_props or a callback from the event.

Utilize it as such: setGridProps({'virtualRowData': rows}) where the prop is the key and the value is the value which you would like to set.

Example setGridProps
import dash_ag_grid as dag
from dash import Dash, html
import dash_mantine_components as dmc

snippet = dmc.Prism(language="Python",children="""
import dash_ag_grid as dag
from dash import Dash, html

app = Dash(__name__)

columnDefs = [
    {"field": "classNames"},
]

rowData = [
    {'classNames': 'ag-theme-alpine'},
    {'classNames': 'ag-theme-balham'},
    {'classNames': 'ag-theme-material'},
    {'classNames': 'ag-theme-quartz'},
    {'classNames': 'ag-theme-alpine-dark'},
    {'classNames': 'ag-theme-balham-dark'},
    {'classNames': 'ag-theme-material-dark'},
    {'classNames': 'ag-theme-quartz-dark'},
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            rowData=rowData,
            eventListeners={'cellFocused': ['setGridProps({className: params.api.getRowNode(params.rowIndex).data.classNames})']}
        ),
        
    ]
)


if __name__ == "__main__":
    app.run(debug=True, port=1236)
    
    """)

app = Dash(__name__, title="setGridProps", update_title=False)

columnDefs = [
    {"field": "classNames"},
]

rowData = [
    {'classNames': 'ag-theme-alpine'},
    {'classNames': 'ag-theme-balham'},
    {'classNames': 'ag-theme-material'},
    {'classNames': 'ag-theme-quartz'},
    {'classNames': 'ag-theme-alpine-dark'},
    {'classNames': 'ag-theme-balham-dark'},
    {'classNames': 'ag-theme-material-dark'},
    {'classNames': 'ag-theme-quartz-dark'},
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            rowData=rowData,
            eventListeners={'cellFocused': ['setGridProps({className: params.api.getRowNode(params.rowIndex).data.classNames})']}
        ),
        snippet
    ]
)


if __name__ == "__main__":
    app.run(debug=True, port=1236)

Here is another example of an advanced method for listening to the filterChanged and storing the advanced filters (AG Grid Enterprise).

In this example, we utilize the new eventListeners along with a newly available JS function (dash 2.16), set_props

We listen to filterChanged and store it into a dcc.Store that can then save the filter to be reapplied later via a dropdown.

Example
import dash_ag_grid as dag
from dash import Dash, html, dcc, Input, Output, State, no_update, Patch
import pandas as pd
import json
import dash_mantine_components as dmc

snippet = dmc.Prism(language="Python",children="""
import dash_ag_grid as dag
from dash import Dash, html, dcc, Input, Output, State, no_update, Patch
import pandas as pd
import json

app = Dash(__name__)


df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)


columnDefs = [
    {"field": "athlete"},
    {"field": "age", "filter": "agNumberColumnFilter", "maxWidth": 100},
    {"field": "country"},
    {
        "headerName": "Date",
        "filter": "agDateColumnFilter",
        "valueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"},
        "valueFormatter": {"function": "params.data.date"},
    },
    {"field": "sport"},
    {"field": "total"},
]

app.layout = html.Div(
    [
        dag.AgGrid(
             id="filter-options-example-simple",
            columnDefs=columnDefs,
            rowData=df.to_dict("records"),
            columnSize="sizeToFit",
            dashGridOptions={"animateRows": False, 'enableAdvancedFilter': True},
            enableEnterpriseModules=True,
            eventListeners={'filterChanged': ['myFilters(params, setGridProps, "gridFilters")']}
        ),
        dcc.Store(id='gridFilters'),
        html.Div(id='filters'),
        html.Button('store filter', id='storeFilter'),
        dcc.Store(id='storedFilters', data=[]),
        dcc.Dropdown(id='storedFilterSelection')
    ]
)

@app.callback(
    Output('filters', 'children'),
    Input('gridFilters', 'data')
)
def showFilters(d):
    if d:
        return json.dumps(d)
    return no_update

@app.callback(
    Output('storedFilters', 'data'),
    Input('storeFilter', 'n_clicks'),
    State('gridFilters', 'data'),
    prevent_initial_call=True
)
def saveFilter(n, d):
    if d:
        new = Patch()
        new.append({'label': f'{n}', 'value': json.dumps(d)})
        return new
    return no_update

@app.callback(
    Output('storedFilterSelection', 'options'),
    Input('storedFilters', 'data')
)
def updateOptions(d):
    if d:
        return d
    return no_update

app.clientside_callback(
    """"""(v, id) => {
        dash_ag_grid.getApi(id).setAdvancedFilterModel(JSON.parse(v))
        return dash_clientside.no_update
    }"""""",
    Output('filter-options-example-simple', 'id'),
    Input('storedFilterSelection', 'value'),
    State('filter-options-example-simple', 'id'),
    prevent_initial_call=True
)


if __name__ == "__main__":
    app.run(debug=True, port=1235)
""")

app = Dash(__name__, title="Advanced Filter", update_title=False)


df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)


columnDefs = [
    {"field": "athlete"},
    {"field": "age", "filter": "agNumberColumnFilter", "maxWidth": 100},
    {"field": "country"},
    {
        "headerName": "Date",
        "filter": "agDateColumnFilter",
        "valueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"},
        "valueFormatter": {"function": "params.data.date"},
    },
    {"field": "sport"},
    {"field": "total"},
]

app.layout = html.Div(
    [
        dag.AgGrid(
             id="filter-options-example-simple",
            columnDefs=columnDefs,
            rowData=df.to_dict("records"),
            columnSize="sizeToFit",
            dashGridOptions={"animateRows": False, 'enableAdvancedFilter': True},
            enableEnterpriseModules=True,
            eventListeners={'filterChanged': ['myFilters(params, setGridProps, "gridFilters")']}
        ),
        dcc.Store(id='gridFilters'),
        html.Div(id='filters'),
        html.Button('store filter', id='storeFilter'),
        dcc.Store(id='storedFilters', data=[]),
        dcc.Dropdown(id='storedFilterSelection'),
        snippet
    ]
)

@app.callback(
    Output('filters', 'children'),
    Input('gridFilters', 'data')
)
def showFilters(d):
    if d:
        return json.dumps(d)
    return no_update

@app.callback(
    Output('storedFilters', 'data'),
    Input('storeFilter', 'n_clicks'),
    State('gridFilters', 'data'),
    prevent_initial_call=True
)
def saveFilter(n, d):
    if d:
        new = Patch()
        new.append({'label': f'{n}', 'value': json.dumps(d)})
        return new
    return no_update

@app.callback(
    Output('storedFilterSelection', 'options'),
    Input('storedFilters', 'data')
)
def updateOptions(d):
    if d:
        return d
    return no_update

app.clientside_callback(
    """(v, id) => {
        dash_ag_grid.getApi(id).setAdvancedFilterModel(JSON.parse(v))
        return dash_clientside.no_update
    }""",
    Output('filter-options-example-simple', 'id'),
    Input('storedFilterSelection', 'value'),
    State('filter-options-example-simple', 'id'),
    prevent_initial_call=True
)


if __name__ == "__main__":
    app.run(debug=True, port=1235)

js file

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

dagfuncs.myFilters = (params, setGridProps, id) => {
    if (params.source !== 'advancedFilter') return
    const data = JSON.stringify(params.api.getAdvancedFilterModel() || {})
    dash_clientside.set_props(id, {data})
}
6 Likes

Thank you for these examples @jinnyzor :muscle:

I like how it’s so much easier to implement an eventListener

1 Like