pythonhtmlpython-3.xplotly-dashdash-bootstrap-components

Python Dash - Is it possible to collapse multiple sections when pressing one button?


I have a dashboard app I'm working on. It has 3 buttons that each collapse/expand a section when clicked. What I'd like to do is have every other section be closed when clicking one of the buttons. I haven't been able to figure out how to make that happen. I can get all the sections to toggle when a button is pressed, but not close. I tried passing a False value to each output but it always just toggled.

If that wasn't clear, here is an example. If button1 is clicked, it will display text1. I'd like it so that if I then press button2, text2 would display and text1 would close. Only one section should ever be displayed at once.

Here is the code as simple as I could make it where each button is collapsing/expanding its section.

import dash
import dash_bootstrap_components as dbc
from dash import html, Input, Output, State


app = dash.Dash(__name__, external_stylesheets=[dbc.themes.DARKLY])
server = app.server


def refresh_layout():
    layout = html.Div(children=[
                dbc.Row([
                    dbc.Col([dbc.Button(
                        "Button1", id="first-button", n_clicks=0, color="dark", style={"width": "100%"})]),
                    dbc.Col([dbc.Button(
                        "Button2", id="second-button", n_clicks=0, color="dark", style={"width": "100%"})]),
                    dbc.Col([dbc.Button(
                        "Button3", id="third-button", n_clicks=0, color="dark", style={"width": "100%"})]),
                ]),

                html.Div(children=[
                    dbc.Collapse([
                        html.Div(children=[
                            dbc.Row([
                                html.H4("Text One", style={"textAlign": "center"}),
                            ]),
                        ]),
                    ], id="first-collapse", is_open=False),
                    dbc.Collapse([
                        html.Div(children=[
                            dbc.Row([
                                html.H4("Text Two", style={"textAlign": "center"}),
                            ]),
                        ]),
                    ], id="second-collapse", is_open=False),
                    dbc.Collapse([
                        html.Div(children=[
                            dbc.Row([
                                html.H4("Text Three", style={"textAlign": "center"}),
                            ]),
                        ]),
                    ], id="third-collapse", is_open=False),
                ])
             ])
    return layout


app.layout = refresh_layout


@app.callback(
    Output("first-collapse", "is_open"),
    Input("first-button", "n_clicks"),
    State("first-collapse", "is_open"),
)
def toggle_first_collapse(num_clicks, is_open):
    if num_clicks:
        return not is_open
    return is_open


@app.callback(
    Output("second-collapse", "is_open"),
    Input("second-button", "n_clicks"),
    State("second-collapse", "is_open"),
)
def toggle_second_collapse(num_clicks, is_open):
    if num_clicks:
        return not is_open
    return is_open


@app.callback(
    Output("third-collapse", "is_open"),
    Input("third-button", "n_clicks"),
    State("third-collapse", "is_open"),
)
def toggle_third_collapse(num_clicks, is_open):
    if num_clicks:
        return not is_open
    return is_open


if __name__ == '__main__':
    app.run_server(debug=False)

Thanks for any help!


Solution

  • You can do this with a single callback. I think this should do the trick.

    @app.callback(
        [
          Output("first-collapse", "is_open"),
          Output("second-collapse", "is_open"),
          Output("third-collapse", "is_open"),
        ],
        [
          Input("first-button", "n_clicks"),
          Input("second-button", "n_clicks"),
          Input("third-button", "n_clicks"),
        ],
    )
    def toggle_collapses(button_one, button_two, button_three):
        ctx = dash.callback_context
    
        if not ctx.triggered:
            raise PreventUpdate
        else:
            button_id = ctx.triggered[0]['prop_id'].split('.')[0]
    
        if button_id == 'first-button':
            return True, False, False
        elif button_id == 'second-button':
            return False, True, False
        elif button_id == 'third-button':
            return False, False, True
        else:
            raise ValueError(f'Unexpected ID: {button_id}')