TODAY learn how to bring Drag & Drop to Dash with Dashboard Engine. 🌠 Sign up for the live event!

Drag and drop cards

Hi everyone,

Is it possible to implement drag and drop cards? Something like this: https://codepen.io/mgmarlow/pen/YwJGRe

or has someone built a new react based component to do this?

I appreciate any help in this regard.

1 Like

You’ve got several options here:

  1. You can create a wrapper for the dragula React component, this requires a bit of knowledge of Javascript, you can find more info on how to do this here
  2. You can use clientside_callback and the external dragula script as shown below (I also use dash_bootstrap_components to style cards)

The Dash app:
app.py

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State, ClientsideFunction


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

app.layout = html.Div(id="main", children=[
    html.Div(id="drag_container", className="container", children=[
        dbc.Card([
            dbc.CardHeader("Card 1"),
            dbc.CardBody(
                "Some content"
            ),
        ]),
        dbc.Card([
            dbc.CardHeader("Card 2"),
            dbc.CardBody(
                "Some other content"
            ),
        ]),
        dbc.Card([
            dbc.CardHeader("Card 3"),
            dbc.CardBody(
                "Some more content"
            ),
        ]),
    ]),
])

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

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

The clientside function:
assets/scripts.js

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

And finally the dragula css:
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);
  }
3 Likes

Thank you very much @RenaudLN! The second option works very well.

Do you know how to build two DIV containers and drag and drop the cards from one container to the another one? I tried duplicating your code (app.py) but it doesn’t allow to go from one container to the other.

Also, is it possible to know (e.g. by a property and a callback) when and which card is dragged and dropped to the other container?

Hi!

I found the solution to drag and drop the cards between containers (still I want to know when and which card is moving), modifying your code. However, I don’t known Javascript, I found the solution by intuition.

First, I modified the code of app.py:

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State, ClientsideFunction


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

app.layout = html.Div(id="main", children=[

    html.Div(id="drag_container0", className="container", children=[

    html.Div(id="drag_container", className="container", children=[
        dbc.Card([
            dbc.CardHeader("Card 1"),
            dbc.CardBody(
                "Some content"
            ),
        ]),
        dbc.Card([
            dbc.CardHeader("Card 2"),
            dbc.CardBody(
                "Some other content"
            ),
        ]),
        dbc.Card([
            dbc.CardHeader("Card 3"),
            dbc.CardBody(
                "Some more content"
            ),
        ]),
    ], style={'padding': 10}) ,
        html.Div(id="drag_container2", className="container", children=[
        dbc.Card([
            dbc.CardHeader("Card a"),
            dbc.CardBody(
                "Some content"
            ),
        ]),
        dbc.Card([
            dbc.CardHeader("Card b"),
            dbc.CardBody(
                "Some other content"
            ),
        ]),
        dbc.Card([
            dbc.CardHeader("Card c"),
            dbc.CardBody(
                "Some more content"
            ),
        ]),
    ], style={'padding': 10} )
 ] )
])

app.clientside_callback(
    ClientsideFunction(namespace="clientside", function_name="make_draggable"),
    Output("drag_container0", "data-drag"),
    [Input("drag_container2", "id"),Input("drag_container", "id")]
)

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

Then, I extended the script.js:

if (!window.dash_clientside) {
    window.dash_clientside = {};
}
window.dash_clientside.clientside = {
    make_draggable: function(id1,id2) {
        setTimeout(function() {
            var el1 = document.getElementById(id1)
            var el2 = document.getElementById(id2)
            window.console.log(el1)
            window.console.log(el2)
            dragula([el1,el2])
        }, 1)
        return window.dash_clientside.no_update
    }
}

Is there a way to build a draggable function with any number of input parameters? Similar to python when you place the *args. That’s to make the javascript function for multiple containers (not just 2).

2 Likes

Regarding adding other containers, you could pass a single argument in the javascript function. This argument would resolve as an array of all the arguments you pass to the function.

make_draggable: function(idArray) {
    //idArray = [id1, id2, ...]
}

You would then need to create the array of DOM elements based on their IDs with document.getElementById.

Regarding your other question, you won’t be able to get when an item is passed from a container to the other with this method. To get all the Dragula events in Dash, you would need to create a proper component.

Thank you again, @RenaudLN.

I’ve modified the function in scripts.js and it works for multiple containers:

if (!window.dash_clientside) {
    window.dash_clientside = {};
}
window.dash_clientside.clientside = {
    make_draggable: function() {
        let args = Array.from(arguments);
        var els = [];
        setTimeout(function() {

            for (i = 0; i < args.length; i++){
                els[i] = document.getElementById(args[i]);
                window.console.log(els[i]);
            }
            dragula(els)
        }, 1)
        return window.dash_clientside.no_update
    }
}


1 Like

This works very well, thank you @RenaudLN !

I have added a callback to display the order of the children of the Div “drag_container” and they appear to stay in the original order even after drag & dropping them.

Would you know how to make the children of the Div reflects the new order of the cards (once dragged & dropped) ?

For the records, the answer to my last question is in Ordering children with drag & drop, how to trigger "dash component has changed" from JS

hi @sdementen ,

You might want to look into dash-draggable: GitHub - MehdiChelh/dash-draggable: Dash components for building draggable and resizable grid layouts. Bas
Its a new library. Ive tried it and it works really well :slight_smile:

Great, thanks for the link!

I see that thz functionality I am looking for “Drag and drop items in list” is on the todo list.

I will check updates of this library in the coming weeks

I have hosted that css and js in case anyone needs it

app = dash.Dash(__name__)

app.config.external_stylesheets = ["https://epsi95.github.io/dash-draggable-css-scipt/dragula.css"]
app.config.external_scripts = ["https://cdnjs.cloudflare.com/ajax/libs/dragula/3.7.2/dragula.min.js",
                               "https://epsi95.github.io/dash-draggable-css-scipt/script.js"]