Column Drill-down using Master-Detail in AG Grid

Hello Everyone !

I would like to create a Master-Detail Dash AG Grid with:

  • Independant columns drill-down
  • Possibility to edit and update the detail grid

Louis Moore’s blog post provides a great example of what I’m trying to do using AG Grid:
https://blog.ag-grid.com/column-drill-down-using-master-detail-ag-grid/

As a JavaScript newbie, I’m currently stuck on two main issues:

  1. Independant columns drill-down

Even if all chevron on the same master row are expanded / collapsed together, It would not be a big issue for me. So I tried first to customize the agGroupCellRenderer to retrieve the Column Id, doing that :

masterColumnDefs = [
    {"headerName": "id", "field": "id"},
    {
        "headerName": "Resources",
        "field": "Resources",
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {
            "innerRenderer": "CustomGroupCellRenderer",
            "innerRendererParams": {"col": "Ressources"},
        },
    },
    {
        "headerName": "Progress",
        "field": "Progress",
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {
            "innerRenderer": "CustomGroupCellRenderer",
            "innerRendererParams": {"col": "Progress"},
        },
    },
]

with:

dagcomponentfuncs.CustomGroupCellRenderer = function (props) {
 return props.col
};

or something like that, but I think that there is no way to use that after in a callback.

Is there a way to retrieve column Id of focused cell when getDetailRequest is triggered within a client server callback ?

  1. Possibility to edit and update the detail grid

I understand that detailCellRendererParams shall be updated each time the getDetailRequest is triggered to keep suppressCallback=False and set the detailGridOptions columnDefs following column Id from which the getDetailRequest has been triggered.

To update my editable detail rows, I tried to affect a customized cellRenderer on each of detail cells and try to do something like :

dagcomponentfuncs.UpdateDetailRows = function (props) {
    const {setData, data} = props;
    function onClick() {
        props.api.gridOptionsService.gridOptions.api.setRowData(data);
        setData(data);
    }
    return React.createElement(
        'div',
        {
            onClick: onClick,
            className: props.className,
        },
        props.value
    );
};

But I am totally lost with js :frowning:

I appreciate any guidance or assistance you can provide.Thank you!

1 Like

Hello @Sam76,

The masterDetail renderer from Dash is designed to work with a specific column, however, there should be no issue that would keep you from doing this.

If you could please provide a full example of what you have tried, that will be most helpful.

Hi @jinnyzor

Thank you very much for your reply.

This is my code:

# -*- coding: utf-8; py-indent-offset:4 -*-

import uuid
import dash_ag_grid as dag
from dash import Dash, html, Input, Output, State
from dash_bootstrap_components import RadioItems

app = Dash(__name__)

masterColumnDefs = [
    {"headerName": "id", "field": "id", "hide": True},
    {"headerName": "Client", "field": "Client", "editable": True},
    {"headerName": "Project", "field": "Project", "editable": True},
    {"headerName": "Discipline", "field": "Discipline", "editable": True},
    {"headerName": "Load", "field": "Load", "editable": True},
    {
        "headerName": "Resources",
        "field": "Resources",
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {
            "innerRenderer": "CustomGroupCellRenderer",
            "innerRendererParams": {"col": "res"},
        },
    },
    {
        "headerName": "Progress",
        "field": "Progress",
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {
            "innerRenderer": "CustomGroupCellRenderer",
            "innerRendererParams": {"col": "av"},
        },
    },
]

detailResourcesDefs = [
    {"headerName": "id2", "field": "id2", "hide": True},
    {
        "headerName": "Resource",
        "field": "Resource",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
    {
        "headerName": "Répartition",
        "field": "Répartition",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
    {
        "headerName": "Profil",
        "field": "Profil",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
]

detailProgressDefs = [
    {"headerName": "id3", "field": "id3", "hide": True},
    {
        "headerName": "Date",
        "field": "Date",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
    {
        "headerName": "Progress",
        "field": "Progress",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
]

rowData = [
    {
        "id": str(uuid.uuid4()),
        "Client": "Client 1",
        "Project": "Project 1",
        "Discipline": "Procédé",
        "Load": 500,
        "Resources": [
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R1",
                "Répartition": "10%",
                "Profil": "Pic à 50%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R2",
                "Répartition": "70%",
                "Profil": "Pic à 25%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R3",
                "Répartition": "20%",
                "Profil": "Pic à 80%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R4",
                "Répartition": "20%",
                "Profil": "Pic à 80%",
            },
        ],
        "Progress": [
            {"id3": str(uuid.uuid4()), "Date": "17/08/2023", "Progress": "10%"},
            {"id3": str(uuid.uuid4()), "Date": "17/11/2023", "Progress": "20%"},
            {"id3": str(uuid.uuid4()), "Date": "17/12/2023", "Progress": "30%"},
        ],
    },
    {
        "id": str(uuid.uuid4()),
        "Client": "Client 1",
        "Project": "Project 2",
        "Discipline": "Installation générale",
        "Load": 5000,
        "Resources": [
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R10",
                "Répartition": "10%",
                "Profil": "Pic à 50%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R11",
                "Répartition": "70%",
                "Profil": "Pic à 25%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R12",
                "Répartition": "20%",
                "Profil": "Pic à 80%",
            },
        ],
        "Progress": [
            {"id3": str(uuid.uuid4()), "Date": "17/08/2023", "Progress": "40%"},
            {"id3": str(uuid.uuid4()), "Date": "17/11/2023", "Progress": "50%"},
            {"id3": str(uuid.uuid4()), "Date": "17/12/2023", "Progress": "60%"},
            {"id3": str(uuid.uuid4()), "Date": "17/01/2024", "Progress": "70%"},
        ],
    },
]

app.layout = html.Div(
    [
        RadioItems(
            id="columnToEdit",
            className="btn-group btn-xs me-1",
            inputClassName="btn-check",
            labelClassName="btn btn-outline-secondary",
            options=[
                {"label": "Resources", "value": "Resources"},
                {"label": "Progress", "value": "Progress"},
            ],
            value="Resources",
        ),
        dag.AgGrid(
            id="aggrid",
            enableEnterpriseModules=True,
            rowData=rowData,
            columnDefs=masterColumnDefs,
            masterDetail=True,
            detailCellRendererParams={
                "suppressCallback": False,
                "suppressDoubleClickExpand": True,
                "detailColName": "Resources",
                "detailGridOptions": {
                    "columnDefs": detailResourcesDefs,
                },
            },
        ),
    ]
)


@app.callback(
    Output("aggrid", "getDetailResponse"),
    Input("aggrid", "getDetailRequest"),
    State("columnToEdit", "value"),
    prevent_initial_call=True,
)
def get_response(request, colToEdit):
    return request["data"][colToEdit]


@app.callback(
    Output("aggrid", "detailCellRendererParams"),
    Input("columnToEdit", "value"),
    prevent_initial_call=True,
)
def update_detailCellRenderParams(colChange):
    return {
        "detailColName": colChange,
        "detailGridOptions": {
            "columnDefs": detailResourcesDefs
            if colChange == "Resources"
            else detailProgressDefs,
            "enterMovesDownAfterEdit": True,
        },
        "suppressCallback": False,
    }


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

I attempted to implement a column drill-down using the Master-Detail feature in AG Grid, inspired by Louis Moore’s blog post. However, I encountered challenges due to my very limited knowledge in JavaScript.

To overcome this issue, I devised a workaround by using RadioItems to select the column for editing and updating. While not as elegant, it serves my current needs.

Currently, I am working on two tasks:

  1. CustomGroupCellRender:
  • I aim to utilize this feature to achieve the following in the Master grid:
    • Display a list of different Resources (as opposed to objects) in the Resources column.
    • Show the maximum value of Progress in the Progress column.
  1. updateDetailData:
  • I intend to update the rowData immediately upon modifying a cell in the details grid.

I have started working on these tasks but lack the expertise in JavaScript to fully implement them."

var dagcomponentfuncs = window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {};

dagcomponentfuncs.CustomGroupCellRenderer = function (props) {
    if (props.colDef.field === "Resources") {
        var resourceNames = props.data.Resources.map(resource => resource.Resource).join(", ");
        return resourceNames;
    } else if (props.colDef.field === "Progress") {
        var progressValues = props.data.Progress.map(progress => progress.Progress);
        var maxProgress = Math.max(...progressValues);
        return maxProgress + "%";
    } else {
        return props.colDef.field;
    }
};

dagcomponentfuncs.updateDetailData = function (props) {
  console.log(props.api.gridOptionsService.gridOptions.rowData)

    const {setData, data} = props;

    console.log(setData)
    console.log(data)

    function onClick() {
        //var newData = props.api.gridOptionsService.gridOptions.rowData
        props.api.gridOptionsService.gridOptions.api.setRowData(data);
        setData(data);
    }
    return React.createElement(
        'div',
        {
            onClick: onClick,
            className: props.className,
        },
        props.value
    );
};

I would be very grateful for a helping hand.
Sam

I tried to replace the RadioItems by getFocusedCell to detect from which column the getDetailRequest has been triggered.

clientside_callback(
    """
    async function(request) {
    const api = await dash_ag_grid.getApiAsync("aggrid");
    const colName = await api.getFocusedCell()?.column.colId;
    const columnValue = await request.data[colName];

    console.log(colName, request.data[colName]);
    
    return [
        columnValue,
        {
            detailColName: colName,
            detailGridOptions: {
                columnDefs: colName === "Resources" ? api.ResourcesDefs : api.ProgressDefs,
                enterMovesDownAfterEdit: true,
            },
            suppressCallback: false,
        },
    ];
    }
    """,
    Output("aggrid", "getDetailResponse"),
    Output("aggrid", "detailCellRendererParams"),
    Input("aggrid", "getDetailRequest"),
    prevent_initial_call=True
)

But I think I have an issue with async / await …

Not sure to really understand how it works …

It seems that global variables can not been taken into account inside the client side callback.

This is working except that I need to click twice the getDetailRequest to print the correct detail grid with:

# -*- coding: utf-8; py-indent-offset:4 -*-

import uuid
import json
import dash_ag_grid as dag
from dash import Dash, html, Input, Output, State, no_update, clientside_callback
from dash_bootstrap_components import RadioItems

app = Dash(__name__)

masterColumnDefs = [
    {"headerName": "id", "field": "id", "hide": True},
    {"headerName": "Client", "field": "Client", "editable": True},
    {"headerName": "Project", "field": "Project", "editable": True},
    {"headerName": "Discipline", "field": "Discipline", "editable": True},
    {"headerName": "Load", "field": "Load", "editable": True},
    {
        "headerName": "Resources",
        "field": "Resources",
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "CustomGroupCellRenderer"},
    },
    {
        "headerName": "Progress",
        "field": "Progress",
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "CustomGroupCellRenderer"},
    },
]

ResourcesDefs = [
    {"headerName": "id2", "field": "id2", "hide": True},
    {
        "headerName": "Resource",
        "field": "Resource",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
    {
        "headerName": "Répartition",
        "field": "Répartition",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
    {
        "headerName": "Profil",
        "field": "Profil",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
]

ProgressDefs = [
    {"headerName": "id3", "field": "id3", "hide": True},
    {
        "headerName": "Date",
        "field": "Date",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
    {
        "headerName": "Progress",
        "field": "Progress",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
]

rowData = [
    {
        "id": str(uuid.uuid4()),
        "Client": "Client 1",
        "Project": "Project 1",
        "Discipline": "Procédé",
        "Load": 500,
        "Resources": [
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R1",
                "Répartition": "10%",
                "Profil": "Pic à 50%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R2",
                "Répartition": "70%",
                "Profil": "Pic à 25%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R3",
                "Répartition": "20%",
                "Profil": "Pic à 80%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R4",
                "Répartition": "20%",
                "Profil": "Pic à 80%",
            },
        ],
        "Progress": [
            {"id3": str(uuid.uuid4()), "Date": "17/08/2023", "Progress": "10%"},
            {"id3": str(uuid.uuid4()), "Date": "17/11/2023", "Progress": "20%"},
            {"id3": str(uuid.uuid4()), "Date": "17/12/2023", "Progress": "30%"},
        ],
    },
    {
        "id": str(uuid.uuid4()),
        "Client": "Client 1",
        "Project": "Project 2",
        "Discipline": "Installation générale",
        "Load": 5000,
        "Resources": [
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R10",
                "Répartition": "10%",
                "Profil": "Pic à 50%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R11",
                "Répartition": "70%",
                "Profil": "Pic à 25%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R12",
                "Répartition": "20%",
                "Profil": "Pic à 80%",
            },
        ],
        "Progress": [
            {"id3": str(uuid.uuid4()), "Date": "17/08/2023", "Progress": "40%"},
            {"id3": str(uuid.uuid4()), "Date": "17/11/2023", "Progress": "50%"},
            {"id3": str(uuid.uuid4()), "Date": "17/12/2023", "Progress": "60%"},
            {"id3": str(uuid.uuid4()), "Date": "17/01/2024", "Progress": "70%"},
        ],
    },
]

app.layout = html.Div(
    [
        html.Div(id="dummy"),
        html.Div(id="resources-defs", style={"display": "none"}, children=json.dumps(ResourcesDefs)),
        html.Div(id="progress-defs", style={"display": "none"}, children=json.dumps(ProgressDefs)),
        RadioItems(
            id="columnToEdit",
            className="btn-group btn-xs me-1",
            inputClassName="btn-check",
            labelClassName="btn btn-outline-secondary",
            options=[
                {"label": "Resources", "value": "Resources"},
                {"label": "Progress", "value": "Progress"},
            ],
            value="Resources",
        ),
        dag.AgGrid(
            id="aggrid",
            enableEnterpriseModules=True,
            rowData=rowData,
            columnDefs=masterColumnDefs,
            masterDetail=True,
            detailCellRendererParams={
                "suppressCallback": False,
                "detailColName": "Resources",
                "detailGridOptions": {
                    "columnDefs": ResourcesDefs,
                    "enterMovesDownAfterEdit": True,
                },
            },
        ),
    ]
)


# @app.callback(
#     Output("aggrid", "getDetailResponse"),
#     Input("aggrid", "getDetailRequest"),
#     State("columnToEdit", "value"),
#     prevent_initial_call=True,
# )
# def get_response(request, colToEdit):
#     return request["data"][colToEdit]


# @app.callback(
#     Output("aggrid", "detailCellRendererParams"),
#     Input("columnToEdit", "value"),
#     prevent_initial_call=True,
# )
# def update_detailCellRenderParams(colChange):
#     return {
#         "detailColName": colChange,
#         "detailGridOptions": {
#             "columnDefs": ResourcesDefs
#             if colChange == "Resources"
#             else detailProgressDefs,
#             "enterMovesDownAfterEdit": True,
#         },
#         "suppressCallback": False,
#     }



clientside_callback(
    """
    async function(request) {
    const api = await dash_ag_grid.getApiAsync("aggrid");
    const colName = api.getFocusedCell()?.column.colId;
    const columnValue = request.data[colName];

    const resourcesDefs = JSON.parse(document.getElementById("resources-defs").textContent);
    const progressDefs = JSON.parse(document.getElementById("progress-defs").textContent);
    
    console.log(colName, columnValue);
    
    return [
        columnValue,
        {
            detailColName: colName,
            detailGridOptions: {
                columnDefs: colName === "Resources" ? resourcesDefs : progressDefs,
                enterMovesDownAfterEdit: true,
            },
            suppressCallback: false,
        },
    ];
    }
    """,
    Output("aggrid", "getDetailResponse"),
    Output("aggrid", "detailCellRendererParams"),
    Input("aggrid", "getDetailRequest"),
    prevent_initial_call=True
)


clientside_callback(
    """
    async function(cellValueChanged) {
        const api = await dash_ag_grid.getColumnApiAsync("aggrid");
        api.autoSizeAllColumns();
        return dash_ag_grid.no_update;
    }
    """,
    Output("dummy", "children"),
    Input("aggrid", "cellValueChanged"),
)

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

Any idea ?

I found a workaround to record updated detail grid using the cell render. (Not very nice … but it works)

Regarding the drill dropdown, it works except that I need to click twice the chevron when I switch from resources to progress (and vice versa).

This is my code:

# -*- coding: utf-8; py-indent-offset:4 -*-

import uuid
import json
import dash_ag_grid as dag
from dash import Dash, html, Input, Output, clientside_callback

app = Dash(__name__)

masterColumnDefs = [
    {"headerName": "id", "field": "id", "hide": True},
    {"headerName": "Client", "field": "Client", "editable": True},
    {"headerName": "Project", "field": "Project", "editable": True},
    {"headerName": "Discipline", "field": "Discipline", "editable": True},
    {"headerName": "Load", "field": "Load", "editable": True},
    {
        "headerName": "Resources",
        "field": "Resources",
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "CustomGroupCellRenderer"},
    },
    {
        "headerName": "Progress",
        "field": "Progress",
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "CustomGroupCellRenderer"},
    },
]

ResourcesDefs = [
    {"headerName": "id2", "field": "id2", "hide": True},
    {
        "headerName": "Resource",
        "field": "Resource",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
    {
        "headerName": "Breakdown",
        "field": "Breakdown",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
    {
        "headerName": "Distribution",
        "field": "Distribution",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
]

ProgressDefs = [
    {"headerName": "id3", "field": "id3", "hide": True},
    {
        "headerName": "Date",
        "field": "Date",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
    {
        "headerName": "Progress",
        "field": "Progress",
        "editable": True,
        "cellRenderer": "agGroupCellRenderer",
        "cellRendererParams": {"innerRenderer": "updateDetailData"},
    },
]

rowData = [
    {
        "id": str(uuid.uuid4()),
        "Client": "Client 1",
        "Project": "Project 1",
        "Discipline": "Process",
        "Load": 500,
        "Resources": [
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R1",
                "Breakdown": "10%",
                "Distribution": "Peak at 50%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R2",
                "Breakdown": "70%",
                "Distribution": "Peak at 25%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R3",
                "Breakdown": "20%",
                "Distribution": "Peak at 80%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R4",
                "Breakdown": "20%",
                "Distribution": "Peak at 80%",
            },
        ],
        "Progress": [
            {"id3": str(uuid.uuid4()), "Date": "17/08/2023", "Progress": "10%"},
            {"id3": str(uuid.uuid4()), "Date": "17/11/2023", "Progress": "20%"},
            {"id3": str(uuid.uuid4()), "Date": "17/12/2023", "Progress": "30%"},
        ],
    },
    {
        "id": str(uuid.uuid4()),
        "Client": "Client 1",
        "Project": "Project 2",
        "Discipline": "General Installation",
        "Load": 5000,
        "Resources": [
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R10",
                "Breakdown": "10%",
                "Distribution": "Peak at 50%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R11",
                "Breakdown": "70%",
                "Distribution": "Peak at 25%",
            },
            {
                "id2": str(uuid.uuid4()),
                "Resource": "R12",
                "Breakdown": "20%",
                "Distribution": "Peak at 80%",
            },
        ],
        "Progress": [
            {"id3": str(uuid.uuid4()), "Date": "17/08/2023", "Progress": "40%"},
            {"id3": str(uuid.uuid4()), "Date": "17/11/2023", "Progress": "50%"},
            {"id3": str(uuid.uuid4()), "Date": "17/12/2023", "Progress": "60%"},
            {"id3": str(uuid.uuid4()), "Date": "17/01/2024", "Progress": "70%"},
        ],
    },
]

app.layout = html.Div(
    [
        html.Div(id="dummy"),
        html.Div(
            id="resources-defs",
            style={"display": "none"},
            children=json.dumps(ResourcesDefs),
        ),
        html.Div(
            id="progress-defs",
            style={"display": "none"},
            children=json.dumps(ProgressDefs),
        ),
        dag.AgGrid(
            id="aggrid",
            enableEnterpriseModules=True,
            rowData=rowData,
            columnDefs=masterColumnDefs,
            masterDetail=True,
            detailCellRendererParams={
                "suppressCallback": False,
                "detailColName": "Resources",
                "detailGridOptions": {
                    "columnDefs": ResourcesDefs,
                    "enterMovesDownAfterEdit": True,
                },
            },
        ),
    ]
)

clientside_callback(
    """
    async function(request) {
        const api = await dash_ag_grid.getApiAsync("aggrid");
        const colName = api.getFocusedCell()?.column.colId;
        return request.data[colName];
    }
    """,
    Output("aggrid", "getDetailResponse"),
    Input("aggrid", "getDetailRequest"),
    prevent_initial_call=True,
)

clientside_callback(
    """
    async function(request) {
        const api = await dash_ag_grid.getApiAsync("aggrid");
        const colName = api.getFocusedCell()?.column.colId;
        const resourcesDefs = JSON.parse(document.getElementById("resources-defs").textContent);
        const progressDefs = JSON.parse(document.getElementById("progress-defs").textContent);
        return {
            detailColName: colName,
            detailGridOptions: {
                columnDefs: colName === "Resources" ? resourcesDefs : progressDefs,
                enterMovesDownAfterEdit: true,
            },
            suppressCallback: false,
        };
    }
    """,
    Output("aggrid", "detailCellRendererParams"),
    Input("aggrid", "getDetailRequest"),
    prevent_initial_call=True,
)

clientside_callback(
    """
    async function(cellValueChanged) {
        const api = await dash_ag_grid.getColumnApiAsync("aggrid");
        api.autoSizeAllColumns();
        return dash_ag_grid.no_update;
    }
    """,
    Output("dummy", "children"),
    Input("aggrid", "cellValueChanged"),
)

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

and in assets/dashAgGridComponentFunctions.js:

var dagcomponentfuncs = window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {};

dagcomponentfuncs.CustomGroupCellRenderer = function (props) {    
    if (props.colDef.field === "Resources") {
        var resourceNames = props.data.Resources.map(resource => resource.Resource).join(", ");
        return resourceNames;
    } else if (props.colDef.field === "Progress") {
        var progressValues = props.data.Progress.map(progress => progress.Progress);
        var maxProgress = Math.max(...progressValues);
        return maxProgress + "%";
    } else {
        return props.colDef.field;
    }
};

dagcomponentfuncs.updateDetailData = function (props) {
    const {data} = props;
    props.api.gridOptionsService.gridOptions.rowData = data

    return React.createElement(
        'div',
        {
            className: props.className,
        },
        props.value
    );
};

This topic might be helpful as well:

2 Likes

Hi AnnMarieW.

Thank you SOOOOO much :blush: !!!

2 Likes