I’m trying to create a assignment interface for categories and colors. Therefore I used some code form this thread: Drag and drop cards
For getting the new order of dragable components into python , the user made, I implemented code from this thread as well:
Ordering children with drag & drop, how to trigger "dash component has changed" from JS
However, when changing the order a couple of times, the wildcard collapse/button components get confused and open wrong collapses. However the divs are in the order, the user chose with dragging and dropping.
I’m out of clues how to deal with that. Pleaase haaalp!
python:
from dash import Dash, html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State, ClientsideFunction, MATCH
import dash_daq as daq
NUM_CATEGORIES = 9
AVAILABLE_CATEGORIES = [f'category_{i}' for i in range(NUM_CATEGORIES)]
# app
app = Dash(__name__,
external_scripts=["https://cdnjs.cloudflare.com/ajax/libs/dragula/3.7.3/dragula.min.js"],
external_stylesheets=[dbc.themes.BOOTSTRAP])
# layout
app.layout = html.Div([
dbc.Row([
# category names
dbc.Col([
html.Div([
dbc.Card([
dbc.CardBody(
f'{category}'
),
])
for category in AVAILABLE_CATEGORIES
]),
]),
# line color
dbc.Col(children=[
# cards
html.Div(id="drag_container_line", className="container", children=[
dbc.Card([
dbc.CardBody(
children=[
# name
f'Card-{i}-line',
# color display + button
dbc.Button(id={'type': 'btn-line-color-picker', 'index': i},
children=html.Label(id={'type': 'line-color', 'index': i},
style={'background': '#FF0000'},
className='color-box'),
className='btn-color-box'),
# collapse + color picker
dbc.Collapse(id={'type': 'collapse-line-color-picker', 'index': i},
children=[
daq.ColorPicker(
id={'type': 'line-color-picker', 'index': i},
value=dict(rgb={'r': 0.9, 'g': 0.2, 'b': 0.1})
)
],
is_open=False
)
]
)
], id=f'Card-{i}-line')
for i in range(NUM_CATEGORIES)
])
], id='col-color-line'),
# buttons
dbc.Button('Apply', id='btn-see-order'),
# order
html.Label(id="order-line"),
])
])
app.clientside_callback(
ClientsideFunction(namespace="clientside", function_name="make_card_draggable"),
[Output("drag_container_line", "data-drag")],
[Input("drag_container_line", "id")],
[State("drag_container_line", "children")]
)
@app.callback(Output("order-line", "children"),
[Input("btn-see-order", "n_clicks"),
Input("drag_container_line", "children")], prevent_initial_call=True)
def _watch_children_line(n, children):
# prints for testing
for child in children:
print(f"label: {child['props']['children'][0]['props']['children'][1]['props']['children']['props']['id']['index']}")
print(f"button: {child['props']['children'][0]['props']['children'][1]['props']['id']['index']}")
print(f"picker: {child['props']['children'][0]['props']['children'][2]['props']['children'][0]['props']['id']['index']}")
print(f"collapse: {child['props']['children'][0]['props']['children'][2]['props']['id']['index']}")
print("___")
return ", ".join([child['props']['id'] for child in children])
@app.callback(Output({'type': 'collapse-line-color-picker', 'index': MATCH}, 'is_open'),
Input({'type': 'btn-line-color-picker', 'index': MATCH}, 'n_clicks'),
State({'type': 'collapse-line-color-picker', 'index': MATCH}, 'is_open'), prevent_initial_call=True)
def _open_fill_color_picker(n, is_open):
return not is_open
if __name__ == '__main__':
app.run_server(debug=True, port=8050)
assets/scripts.js
if (!window.dash_clientside) {
window.dash_clientside = {};
}
window.dash_clientside.clientside = {
make_card_draggable: function (id, children) {
setTimeout(function () {
const drake = dragula({});
const el = document.getElementById(id);
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)
});
})
}, 1)
return window.dash_clientside.PreventUpdate;
}
}
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);
}
.color-box {
width: 2rem;
height: 2rem;
border-radius: 4px;
}
.btn-color-box, .btn-color-box:active, .btn-color-box:focus, .btn-color-box:hover {
background: transparent;
padding: 0;
border: none;
}
.btn-color-box:hover {
filter: brightness(70%);
}