Append children based on client side callback

How can I update the children of a component whenever an element is dragged and dropped onto it? I’m running way before I’m crawling with JavaScript, so I’m a little unsure how to accomplish this.

I think what I need to do to start is have a clientside_callback that will appendChild the element being dragged/dropped to the parent element I’m dropping on. Then I need to trigger a callback whenever the children in the parent element changes so that the I can use a python function to update the table. Am I on the right track here? Can anyone point to or provide an example of what I’m trying to accomplish?

Here’s my code if you want to try some stuff out:

app.py

from dash import Dash, dcc, html, dash_table, callback, clientside_callback
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State, ClientsideFunction

import pandas as pd

df = pd.DataFrame({
    'Parameter 1': [i for i in range(10)],
    'Parameter 2': [i + 2 for i in range(10)],
    'Parameter 3': [i * 2 for i in range(10)],
    'Parameter 4': [i / 2 for i in range(10)],
    'Parameter 5': [i ** 2 for i in range(10)]
})
list_of_parameters = [dbc.Card(dbc.CardHeader(i, className="card-container")) for i in df.columns]

app = Dash(
    __name__,
    external_scripts=["https://cdnjs.cloudflare.com/ajax/libs/dragula/3.7.2/dragula.min.js"],
    external_stylesheets=[dbc.themes.BOOTSTRAP],
    prevent_initial_callbacks=True
)

app.layout = html.Div(
    id="parameter-container",
    children=[
        dbc.Card([
            dbc.CardHeader("Available Parameters"),
            dbc.CardBody([
                dbc.Button(
                    "Show",
                    id="parameter-collapse-button"
                ),
                dbc.Collapse(
                    html.Div(
                        id="parameter-collapse-div",
                        children=list_of_parameters[2:],
                    ),
                    id="parameter-collapse",
                    is_open=False,
                )
            ])
        ]),
        dbc.Card([
            dbc.CardHeader("Selected Parameters"),
            dbc.CardBody(
                id="selected-parameter-div",
                children=list_of_parameters[:2],
            )
        ]),
        dash_table.DataTable(
            id="data-table",
            columns=[],
            data=df.to_dict('records'),
            filter_action="native",
            sort_action="native",
            page_action="native",
            page_current=0,
            page_size=10,
        )
    ]
)


@callback(
    Output("parameter-collapse", "is_open"),
    [Input("parameter-collapse-button", "n_clicks")],
    [State("parameter-collapse", "is_open")]
)
def toggle_collapse(nclicks, is_open):
    if nclicks:
        return not is_open
    return is_open


clientside_callback(
    ClientsideFunction(namespace="clientside", function_name="make_draggable"),
    Output("parameter-container", "data-drag"),
    [
        Input("parameter-collapse-div", "id"),
        Input("selected-parameter-div", "id")
    ]
)


@callback(
    Output("data-table", "columns"),
    [Input("selected-parameter-div", "children")]
)
def update_columns(parameters):
    # parameters should always include everything in selected-parameter-div
    # and not just want it is initalized with.
    if parameters:
        cols = []
        for parameter in parameters:
            cols.append(parameter['props']['children']['props']['children'])
        return [{'name': c, 'id': c, 'deletable': False} for c in cols]


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

assets/scripts.js

if (!window.dash_clientside) {
    window.dash_clientside = {};
}
window.dash_clientside.clientside = {
    make_draggable: function(rightID, leftID) {
        setTimeout(function() {
            var rightEl = document.getElementById(rightID)
            var leftEl = document.getElementById(leftID)
            window.console.log(rightEl)
            window.console.log(leftEl)
            dragula([rightEl, leftEl])
        }, 1)
        return window.dash_clientside.no_update
    }
}

assets/dragula.css

.gu-mirror {
    position: fixed !important;
    margin: 0 !important;
    z-index: 9999 !important;
    opacity: 0.8;
    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
    filter: alpha(opacity=80);
}

.gu-hide {
    display: none !important;
}

.gu-unselectable {
    -webkit-user-select: none !important;
    -moz-user-select: none !important;
    -ms-user-select: none !important;
    user-select: none !important;
}

.gu-transit {
    opacity: 0.2;
    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
    filter: alpha(opacity=20);
}

assets/styles.css

#parameter-container {
    margin: 1%;
}

.card-container {
    display: inline-block;
}

Hi,

Here are related discussions on this type of component:

As I mentioned in the last post above, this clientside callback function is called for its side-effects and I don’t see how it can be used to update props on Dash components.