Capture and Save filter expression from Advanced Filters in Python Dash Ag-Grid

Hi, @jinnyzor!

Thanks for your response.

I could implement the same using Grid APIs.

Kindly take a look at my code as well and let me know your views.

from dash import dash, Dash, dcc, html, Input, Output, State, clientside_callback
import pandas as pd
import dash_ag_grid
import dash_mantine_components as dmc
import json

option_mapping = {
    "contains": "contains",
    "notContains": "does not contain",
    "startsWith": "begins with",
    "endsWith": "ends with",
    "blank": "is blank",
    "notBlank": "is not blank",
    "greaterThan": ">",
    "greaterThanOrEqual": ">=",
    "lessThan": "<",
    "lessThanOrEqual": "<=",
    "true": "is true",
    "false": "is false",
    "equals": {"text": "equals", "object": "equals", "number": "=", "date": "=", "dateString": "="},
    "notEqual": {"text": "not equal", "object": "not equal", "number": "!=", "date": "!=", "dateString": "!="},
}

def format_filter_expression(filter_dict):
    if "filterType" not in filter_dict:
        return ""

    filter_type = filter_dict.get("filterType")
    if filter_type == "join":
        join_type = filter_dict.get("type", "AND")
        conditions = filter_dict.get("conditions", [])
        formatted_conditions = [format_filter_expression(cond) for cond in conditions]
        return f"({f' {join_type} '.join(formatted_conditions)})"

    elif filter_type in ["text", "number"]:
        col_id = filter_dict.get("colId", "")
        filter_type_key = filter_dict.get("type", "")
        filter_value = filter_dict.get("filter", "")

        # Determine the data type
        data_type = "text"  # Default to "text" if not specified
        if filter_type == "number":
            data_type = "number"
        # Add more conditions if needed to infer data_type

        # Replace filter type using option_mapping
        if filter_type_key in option_mapping:
            filter_type = option_mapping[filter_type_key]
            if isinstance(filter_type, dict):
                filter_type = filter_type.get(data_type, filter_type.get("text"))  # Default to "text" if specific data type is not found
        else:
            filter_type = filter_type_key

        # Format filter value appropriately
        if data_type == "text":
            return f"[{col_id}] {filter_type} \"{filter_value}\""
        else:
            return f"[{col_id}] {filter_type} {filter_value}"

    else:
        return ""
    
    
style_cell = {
    "fontFamily": "Inter",
    "fontSize": 14,
    "height": "30vh",
    "minWidth": "100%",
    "width": "100%",
    "maxWidth": "100%",
    "padding": "0.5rem",
    "textAlign": "center",
    "fontWeight": "normal",
}



data = {
    'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eva', 'Frank', 'Grace', 'Hannah', 'Ivy', 'Jack'],
    'City': ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix', 'Philadelphia', 'San Antonio', 'San Diego', 'Dallas', 'San Jose'],
    'Age': [25, 30, 35, 40, 28, 32, 27, 45, 29, 31],
    'Gender': ['Female', 'Male', 'Male', 'Male', 'Female', 'Male', 'Female', 'Female', 'Female', 'Male'],
    'Salary': [70000, 80000, 120000, 90000, 75000, 85000, 95000, 105000, 60000, 78000]
}
df = pd.DataFrame(data)

grid = dash_ag_grid.AgGrid(
    id='ag-grid',
    columnDefs=[{"headerName": c, "field": c} for c in df.columns],
    rowData=df.to_dict("records"),
    className="ag-theme-balham color-setting",
    rowStyle={"defaultStyle": style_cell},
    dashGridOptions={
        "rowHeight": 25,
        "pagination": True,
        "paginationPageSize": 5,
        "icons": {
            "columnGroupOpened": '<i class="fa-regular fa-square-check"></i>',
            "columnGroupClosed": '<i class="fa-regular fa-square"></i>',
        },
        "domLayout": "autoHeight",
        "tooltipShowDelay": 100,
        "postSortRows": {"function": "postSort(params)"},
        "enableAdvancedFilter": True
    },
    enableEnterpriseModules=True,
    columnSize="autoSize",
    style={"height": "100%", "width": "100%"},
    defaultColDef={"editable": True, "filter": True, "floatingFilter": True, "filterParams": {"maxNumConditions": 6, "buttons": ["reset"], "closeOnApply": True}, "wrapText": True, "autoHeight": True, "cellStyle": {"wordBreak": "normal", "lineHeight": "unset"}},
)

grid2 = dash_ag_grid.AgGrid(
    id='ag-grid2',
    columnDefs=[{"headerName": c, "field": c} for c in df.columns],
    rowData=df.to_dict("records"),
    className="ag-theme-balham color-setting",
    rowStyle={"defaultStyle": style_cell},
    dashGridOptions={
        "rowHeight": 25,
        "pagination": True,
        "paginationPageSize": 5,
        "icons": {
            "columnGroupOpened": '<i class="fa-regular fa-square-check"></i>',
            "columnGroupClosed": '<i class="fa-regular fa-square"></i>',
        },
        "domLayout": "autoHeight",
        "tooltipShowDelay": 100,
        "postSortRows": {"function": "postSort(params)"},
        "enableAdvancedFilter": True
    },
    enableEnterpriseModules=True,
    columnSize="sizeToFit",
    style={"height": "100%", "width": "100%", 'display':'none'},
    defaultColDef={"editable": True, "filter": True, "floatingFilter": True, "filterParams": {"maxNumConditions": 6, "buttons": ["reset"], "closeOnApply": True}, "wrapText": True, "autoHeight": True, "cellStyle": {"wordBreak": "normal", "lineHeight": "unset"}},
)


app = Dash(__name__)

app.layout = html.Div([
    html.Div(id='filter-expression-display'),
    dcc.Store(id='filter-expression-store', data=None),    
    dmc.Space(h=30), 
    grid,
    html.Div(id='filter-expression-display2'),
    dcc.Store(id='filter-expression-store2', data=None),    
    dmc.Space(h=30), 
    grid2
])


clientside_callback(
    """
    async (n1, n2, button) => {
        try {
            const gridApi1 = await dash_ag_grid.getApiAsync("ag-grid");
            const gridApi2 = await dash_ag_grid.getApiAsync("ag-grid2");

            if (!gridApi1 || !gridApi2) {
                console.error("Grid API not found or not initialized.");
                return [dash_clientside.no_update, dash_clientside.no_update];
            }
            
            // Get the current advanced filter model for grid 1
            const filterModel1 = gridApi1.getAdvancedFilterModel();
            // Get the current advanced filter model for grid 2
            const filterModel2 = gridApi2.getAdvancedFilterModel();

            // Convert the filter models to JSON strings for display
            const filterModelJson1 = JSON.stringify(filterModel1, null, 2);
            const filterModelJson2 = JSON.stringify(filterModel2, null, 2);

            return [filterModelJson1, filterModelJson2];
        } catch (error) {
            console.error("Error getting grid API or filter model:", error);
            return [dash_clientside.no_update, dash_clientside.no_update];
        }
    }
    """,
    Output('filter-expression-store', 'data'),
    Output('filter-expression-store2', 'data'),    
    Input("ag-grid", "virtualRowData"),
    Input("ag-grid2", "virtualRowData"), 
    
)

# Add a callback to display the filter expression
@app.callback(
    Output('filter-expression-display', 'children'),
    Input('filter-expression-store', 'data'),
)
def display_filter_expression(data):
    # print(data)
    if data:        
        return f"Current Filter Expression: {format_filter_expression(json.loads(data))}"
    return "No filters applied."



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