Adding and removing bootstrap cards to DASH with automatic grid layout in Python
Question:
What I would like to do is when adding a button interactively as in this example (https://github.com/facultyai/dash-bootstrap-components/issues/374#issuecomment-629619851 with this code https://gist.github.com/tcbegley/b7c49d0aec605e383d3b8190448f45f2), to be able to have any kind of grid and not a vertical one where all the cards are stacked on top of each other. I would like to know if you can do so that for example when you create the first card, this is positioned to the left or centre of a first Row, when you create the second one, the first one is moved to the left and the second one is positioned to the right of the first one, making a grid of for example up to 4 rows in this way:
x x
x x
x x
x x
If a card is to be removed, the other cards must be repositioned to leave the gap at the position of the last card.
x x
x x
x x
x
At the moment it generates the cards vertically 1 below the other. Any ideas on how to do this?
Thanks!
Answers:
All you need to do is to replace html.Div([], id="output")
with dbc.Row([], style={"width":"850px"},id="output")
and everything works seamlessly:
Full example:
import json
import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import ALL, Input, Output, State
from dash.exceptions import PreventUpdate
FONT_AWESOME = "https://use.fontawesome.com/releases/v5.13.0/css/all.css"
app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP, FONT_AWESOME])
app.layout = dbc.Container(
[
dbc.InputGroup(
[
dbc.Input(id="input"),
dbc.Button("Add card", id="add-button")
],
className="mb-4",
),
dbc.Row([], style={"width":"850px"},id="output"),
],
className="p-5",
)
def make_card(n_add, content):
return dbc.Card(
[
dbc.CardHeader(
[
"Card header",
html.Button(
html.I(className="fas fa-times"),
className="ml-auto close",
id={"type": "close-button", "index": n_add},
),
]
),
dbc.CardBody(html.P(content, className="card-text")),
],
id={"type": "card", "index": n_add},
style={"width": "400px"},
className="mb-3 mx-auto",
)
@app.callback(
Output("output", "children"),
[
Input("add-button", "n_clicks"),
Input({"type": "close-button", "index": ALL}, "n_clicks"),
],
[
State("input", "value"),
State("output", "children"),
State({"type": "close-button", "index": ALL}, "id"),
],
)
def manage_cards(n_add, n_close, content, children, close_id):
ctx = dash.callback_context
if not ctx.triggered:
raise PreventUpdate
else:
button_id, _ = ctx.triggered[0]["prop_id"].split(".")
if button_id == "add-button":
if n_add:
if children is None:
children = []
children.append(make_card(n_add, content))
else:
print(button_id)
button_id = json.loads(button_id)
index_to_remove = button_id["index"]
children = [
child
for child in children
if child["props"]["id"]["index"] != index_to_remove
]
return children
if __name__ == "__main__":
app.run_server()
What I would like to do is when adding a button interactively as in this example (https://github.com/facultyai/dash-bootstrap-components/issues/374#issuecomment-629619851 with this code https://gist.github.com/tcbegley/b7c49d0aec605e383d3b8190448f45f2), to be able to have any kind of grid and not a vertical one where all the cards are stacked on top of each other. I would like to know if you can do so that for example when you create the first card, this is positioned to the left or centre of a first Row, when you create the second one, the first one is moved to the left and the second one is positioned to the right of the first one, making a grid of for example up to 4 rows in this way:
x x
x x
x x
x x
If a card is to be removed, the other cards must be repositioned to leave the gap at the position of the last card.
x x
x x
x x
x
At the moment it generates the cards vertically 1 below the other. Any ideas on how to do this?
Thanks!
All you need to do is to replace html.Div([], id="output")
with dbc.Row([], style={"width":"850px"},id="output")
and everything works seamlessly:
Full example:
import json
import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import ALL, Input, Output, State
from dash.exceptions import PreventUpdate
FONT_AWESOME = "https://use.fontawesome.com/releases/v5.13.0/css/all.css"
app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP, FONT_AWESOME])
app.layout = dbc.Container(
[
dbc.InputGroup(
[
dbc.Input(id="input"),
dbc.Button("Add card", id="add-button")
],
className="mb-4",
),
dbc.Row([], style={"width":"850px"},id="output"),
],
className="p-5",
)
def make_card(n_add, content):
return dbc.Card(
[
dbc.CardHeader(
[
"Card header",
html.Button(
html.I(className="fas fa-times"),
className="ml-auto close",
id={"type": "close-button", "index": n_add},
),
]
),
dbc.CardBody(html.P(content, className="card-text")),
],
id={"type": "card", "index": n_add},
style={"width": "400px"},
className="mb-3 mx-auto",
)
@app.callback(
Output("output", "children"),
[
Input("add-button", "n_clicks"),
Input({"type": "close-button", "index": ALL}, "n_clicks"),
],
[
State("input", "value"),
State("output", "children"),
State({"type": "close-button", "index": ALL}, "id"),
],
)
def manage_cards(n_add, n_close, content, children, close_id):
ctx = dash.callback_context
if not ctx.triggered:
raise PreventUpdate
else:
button_id, _ = ctx.triggered[0]["prop_id"].split(".")
if button_id == "add-button":
if n_add:
if children is None:
children = []
children.append(make_card(n_add, content))
else:
print(button_id)
button_id = json.loads(button_id)
index_to_remove = button_id["index"]
children = [
child
for child in children
if child["props"]["id"]["index"] != index_to_remove
]
return children
if __name__ == "__main__":
app.run_server()