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);
  }
5 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
    }
}


2 Likes

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

1 Like

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:

1 Like

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"]

Is it possible to get the new order of items after the items are dragged and dropped? I am using the data-drag property of the drag container to retrieve the modified list of items but data-drag seems to be None. I need to update another component of the app based on this modified list of items.

Any help would be much appreciated!

Hello @ray26,

Yes, for this, you’d need a clientside callback to get the order of the current div and send it to a dcc.Store or something to be used in reference by your app.

Hi @jinnyzor, thanks for your reply. I have the following clientside callback function, where 'drag-priorities' is the container of draggable cards:

app.clientside_callback(
    ClientsideFunction(namespace='clientside', function_name='make_draggable'),
    Output('drag-priorities', 'data-drag'),
    Input('drag-priorities', 'id'),
)

Here is the JS file containing the make_draggable function:

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
    }
}

Should the order of the current div ('drag-priorities') be retrieved within the make_draggable function in the JS code above? If so, may I get some help in doing so? Thanks!

No, make_draggable is a onetime thing.

The easiest way would be to make a button to resync the data. Once you get that working, you can add event listeners to the drop event of the individual items and have it click the button.

Is it possible to make the items in a dynamic container draggable? The code examples above seem to work fine if a container for the draggable items is predefined in the page layout. However, my draggable items’ container is dynamically created after the user presses a button and I want the items in it to be draggable. The current JavaScript code examples do not work for my case; I cannot move the draggable items at all in the dynamically created container.

I do not know much JavaScript so would greatly appreciate sample JavaScript code (or any help really)! Thanks :slight_smile:

It is possible, see the discussion here: Ordering children with drag & drop, how to trigger “dash component has changed” from JS - Dash Python - Plotly Community Forum