Ordering children with drag & drop, how to trigger "dash component has changed" from JS

These tweaks works for me.

This works with dynamic children in the drag-container and does trigger an update of children upon reordering:

App.py:

"""
App for testing drag and drop

"""


import dash
import dash_html_components as html
from dash.dependencies import Input, Output, ClientsideFunction, State


SERVER_PORT = 12327

app = dash.Dash(
    __name__,
    #external_scripts=["https://cdnjs.cloudflare.com/ajax/libs/dragula/3.7.2/dragula.min.js"],
)

app.layout = html.Div(
    id="main",
    children=[
        html.Button(id="btn", children="Refresh display for order of children"),
        html.Label(id="order"),
        html.Div(id="container"),
        html.Button(id="btn_children", children="new children")
    ],
)

app.clientside_callback(
    ClientsideFunction(namespace="clientside", function_name="make_draggable"),
    Output("drag_container", "data-drag"),
    Input("drag_container", "children"),
    State("drag_container", "id")

)

@app.callback(
    Output('container', 'children'),
    Input('btn_children', 'n_clicks'),
    prevent_initial_call=True
)
def test(n_clicks):
    print("running")

    if (n_clicks % 2) == 0:
        range_ = range(5)
    else:
        range_ = range(5,10)

    a = html.Div(
        id="drag_container",
        className="container",
        children=[html.Button(id=f"child-{i}", children=f"child-{i}") for i in range_],
    ),
    return(a)





@app.callback(
    Output("order", "children"),
    [
        Input(component_id="btn", component_property="n_clicks"),
        Input(component_id="drag_container", component_property="children"),
    ],
    prevent_initial_call = True
)
def watch_children(nclicks, children):
    """Display on screen the order of children"""
    return ", ".join([comp["props"]["id"] for comp in children])


if __name__ == '__main__':
	app.run_server(host="0.0.0.0", port=SERVER_PORT ,debug=True, dev_tools_hot_reload=True)

Script.js

if (!window.dash_clientside) {
    window.dash_clientside = {};
}
window.dash_clientside.clientside = {
    make_draggable: function (children, id) {
        setTimeout(function () {

            var drake = dragula({});
            var el = document.getElementById(id)
            var order = document.getElementById("order")
            drake.containers.push(el);
            drake.on("drop", function (_el, target, source, sibling) {
                // a component has been dragged & dropped
                // get the order of the ids from the DOM
                var order_ids = Array.from(target.children).map(function (child) {
                    return child.id;
                });
                // in place sorting of the children to match the new order
                children.sort(function (child1, child2) {
                    return order_ids.indexOf(child1.props.id) - order_ids.indexOf(child2.props.id)
                });
                // Trigger an update on the children property
                order.innerHTML = order_ids
            })
        }, 1)
        return window.dash_clientside.no_update
    }
}

2 Likes