dbc.Switch() component in the DataTable

Hey guys! How would you add dbc.Switch() to a DataTable so that a boolean column is visualized by switches instead of “true” or “false”? Is that even possible? Thanks!

HI @nikola.mirkov

At this time, you can’t put components in DataTable cells. A workaround is to use an HTML Table.

1 Like

Hi @AnnMarieW is there any change regarding this topic? Thanks :slight_smile:

Hey @dashamateur !

Consider switching to ag-grid.

1 Like

Hey @AIMPED thanks for the reply! I tried avoiding js but i guess it will be necessary :sweat_smile:

Hi @dashamateur

In the next major release of Dash AG Grid, the boolean values will automatically render as check boxes. No custom components required :tada:

Until then, you can see examples of how to add components to cells in the docs. If you would like a dbc.Switch instead of a checkbox, you can find an example here:

There shouldn’t be too much JavaScript coding involved if you copy one of the examples, but if you have any questions, feel free to ask for help here :slight_smile:

1 Like

Hi @AnnMarieW and thanks for the reply! My problem is a bit more specific as I’m trying to render a substring inside a cell. I am currently doing this with an if sentence:

// INPUT
dagcomponentfuncs.DBC_Input = function (props) {
    const { type, value, placeholder } = props;

    let string = props.data.check;

    if (string.includes('INPUT')) {
        // Replace 'INPUT' with dbc.Input component
        string = string.replace(
            'INPUT',
            React.createElement(
                window.dash_bootstrap_components.Input,
                {
                    type: type || 'text',
                    value: value || '',
                    placeholder: placeholder || '0,00',
                }
            )
        );
    }


    return string
};

which finds the substring INPUT and renders it but it returns [object Object]. I am still trying to find a remedy for that. Do you have any suggestions on how to render specific substrings into dbc components? Thanks alot!

EDIT:
Problem was in the types of data i was returning because it converted dbc.Input to string. After some struggle i managed to get it to work as wanted (code below). I am open to cleaner solutions :slight_smile: Also, how would you capture updated grid to include the user inputs? I have created a button that reads the data: State(“ag-grid”, “rowData”), but it doesn’t include rendered cells. Is there a workaround to “scrape” the grid? Thanks!

// INPUT
dagcomponentfuncs.DBC_Input = function (props) {
    const { type,handleInputChange,placeholder } = props;

    let parts = props.data.check.split('INPUT');
    let elements = [];

    // Iterate over parts and add both strings and React elements to the array
    parts.forEach((part, index) => {
        elements.push(part);
        if (index < parts.length - 1) {
            // Insert the React element
            elements.push(
                React.createElement(
                                window.dash_bootstrap_components.Input,
                                {
                                    type: type || 'text',
                                    onChange: handleInputChange,
                                    placeholder: placeholder || '0,00',
                                    style: {
                                        display: "inline-block",
                                        width: "20%",
                                    },
                                }
                            )
            );
        }
    });

    return React.createElement('div', null, elements);
};

For anyone with this problem: rendering part of ag-grid cell this is how i solved it for myself. I wanted to replace part of string in a cell with dbc.Input and keep the components functionality so I would be able to use the users entered data. I achived this with this function in a .js file:

dagcomponentfuncs.DBC_Input_With_String = function (props) {
    const { setData, handleInputChange, data } = props;
    const [inputValues, setInputValues] = React.useState(Array(props.data.check.split(/(%%.*?%%)/).length).fill(''));

    const setProps = (index, inputValue) => {
        const combinedValue = data.check.split(/(%%.*?%%)/).map((part, index2) => {
            if (index2 % 2 === 0) {
                return part;
            } else if (index === Math.floor(index2)) {
                return `%%${inputValue}%%`;
            } else {
                return part;
            }
        }).join('');
    
        props.node.setDataValue(props.column.colId, combinedValue);
    
        setData(prevData => ({
            ...prevData,
            check: combinedValue,
        }));
    };
    

    const handleChange = (index, event) => {
        const newInputValues = [...inputValues];
        newInputValues[index] = event.target.value;
        setInputValues(newInputValues);
        if (handleInputChange) {
            handleInputChange(event);
        }
    };

    return React.createElement(
        'div',
        null,
        data.check.split(/(%%.*?%%)/).map((part, index) => {
            if (part.match(/%%.*?%%/)) {
                // If it matches the pattern, render Input element
                return React.createElement(
                    window.dash_bootstrap_components.Input,
                    {
                        key: index,
                        type: props.type || 'text',
                        placeholder: '0,00',
                        setProps: () => setProps(index, inputValues[index]),
                        onChange: (event) => handleChange(index, event),
                        value: inputValues[index],
                        style: {
                            display: 'inline-block',
                            width: 'auto',
                        },
                    }
                );
            } else {
                // Otherwise, render only Label element
                return React.createElement(
                    window.dash_bootstrap_components.Label,
                    {
                        key: index,
                        children: part,
                        style: {
                            display: 'inline-block',
                            width: 'auto',
                        },
                    }
                );
            }
        }),
    );
};

Inside of the dash app you have to define ag-grid:

                    dag.AgGrid(
                        id="ag-grid",
                        rowData=[],
                        columnSize="autoSize",
                        dashGridOptions={
                            "rowHeight": 48,
                            "rowSelection": "multiple",
                        },
                        columnDefs=[
                            {
                                "headerName": "Check this column:",
                                "field": "check",
                                "cellRenderer": "DBC_Input_With_String",
                            },
                        ],
                    ),

Then i used a callback to populate the grid. The trick here is that you define a place where you want your input to be with this placeholder: %%INPUT%%. Javascript will look for it and render value inside %%*%%.

@callback(
    [
        Output("ag-grid", "rowData"),
        Output("ag-grid", "defaultColDef"),
    ],
    [
        Input("interval-component", "n_intervals"),
    ],
)
def ag_grid_formation(n_intervals):
    data = {
        "check": [
            "First input: %%INPUT%% Second input: %%INPUT%%",
        ],
    }
    df = pd.DataFrame(data)

    defaultColDef = {
        "resizable": False,
        "sortable": False,
        "editable": False,
    }

    return df.to_dict("records"), defaultColDef

Now the rowData is sorted. For data manipulation and further usage you can create another callback or use the previous one. I created a new one since I was creating a form and wanted to read the whole form once the user would click the button “SEND”. In this callback I also added some data rendering since the placeholder was written like this: %%INPUT%%.

@callback(
    [
        Output("dummy", "children"),
    ],
    [Input("button-grid", "n_clicks")],
    [State("ag-grid", "rowData")],
)
def ag_grid(click, data):
    print(data)
    updated_data_list = [
        {
            "check": item["check"]
            .replace("%%", "")
            .replace("%%INPUT%%", "No value entered"),
        }
        for item in data
    ]
    print(updated_data_list)
    return dash.no_update

That’s it :smiley: