Show and Tell - Accordeon with pattern matching callbacks + callback_context

Hey guys,

I have created an accordion component using pattern matching callback + calback_context

As the pattern matching component is so powerful, I’m sure that it can be useful to some people, so I would like to share with you all the solution I found, but would love to know if it exists a better way to do this


import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output, State, ALL
import dash

# CSS files and links
external_stylesheets = [dbc.themes.BOOTSTRAP,  
                        'https://use.fontawesome.com/releases/v5.8.1/css/all.css',
                        ]

app=dash.Dash(__name__, 
                external_stylesheets=external_stylesheets, 
                meta_tags=[{"name": "viewport", 
                            "content":"width=device-width, initial-scale=1, maximum-scale=1"
                            }],)

example_json={"Options1":["example1", "example2", "example3"],
              "Option2":["example4", "example5", "example6"],
              "Option3":["example7", "example8", "example9"]}

def accordeon_comp(i):
    internal_options_list = example_json[i]
    return html.Div(
        [
            html.Div(
                html.Div([
                    dbc.Button([html.Span(html.I(className="fas fa-plus plus-color-accord", 
                                                 id={
                                                    'type': 'font-awesome-plus-icon',
                                                    'index': i
                                                    }
                                                )), f"  {i}"],
                        color="black",
                        id={
                            'type': 'accord-btn',
                            'index': i
                        },
                        n_clicks=0,
                    ),
                        dbc.Collapse(

                        [dbc.Card(html.Div([card_name], 
                                           id={
                                                'type': 'industry-options',
                                                'index': card_name
                                            }
                                          ), style={"width":"250px"}
                                 ) for card_name in internal_options_list],
                        id={
                            'type': 'content-accordeon',
                            'index': i
                        },
                        is_open=False,
                    )]
                )
            ),
            ])


accordion_component = html.Div(
    [accordeon_comp(val) for val in [val for val in list(example_json.keys())]], 
    className="accordion"
)

app.layout = html.Div(accordion_component)


@app.callback(
    Output({'type': 'content-accordeon', 'index': ALL}, 'is_open'),
    Output({'type': 'font-awesome-plus-icon', 'index': ALL}, 'className'),
    Input({'type': 'accord-btn', 'index': ALL}, 'n_clicks'),
    State({'type': 'content-accordeon', 'index': ALL}, 'is_open')
)
def display_output(_, is_open):

    ctx = dash.callback_context

    if not ctx.triggered:
        plus_open_icon="fas fa-plus"
        plus_close_icon="fas fa-minus"
        return is_open, [plus_open_icon if entry == False else plus_close_icon for entry in is_open]
    else:
        button_id = ctx.triggered[0]['prop_id'].split('.')[0]

    triggered=eval(button_id)['index']
    state_object=ctx.states
    inputs=ctx.inputs

    is_open_original=is_open.copy()
    
    is_open = [True if eval(val.split(".")[0])['index'] == triggered else False for val in state_object]    
    
    index_change = [n for n, val in enumerate(state_object) if eval(val.split(".")[0])['index'] == triggered]    
    
    if is_open_original == is_open:
        is_open[index_change[0]] = not is_open[index_change[0]]
    else:
        pass

    plus_open_icon="fas fa-plus"
    plus_close_icon="fas fa-minus"

    return is_open, [plus_open_icon if entry == False else plus_close_icon for entry in is_open]


if __name__ == '__main__':
    app.run_server(debug=True, port="4487")
    

It is an adaptation of the accordion shown on the dbc.collapse;

Hope that it can be useful to someone;

REgards,

2 Likes

Hello, I tried you get your code to work but when I click any of the buttons, the callback does not fire. Nothing happens.
Is this still a valid code example?

Regards,

Hi @lanningb and welcome to the Dash community :slight_smile:

That example may have been from before the dbc.Accordion component was added to the dash-bootstrap-components library. It’s available now:

https://dash-bootstrap-components.opensource.faculty.ai/docs/components/accordion/

There is also an Accordion componen in the dash-mantine-components library:

Thank you!

So is there a way to open and close the Accordion through a callback?
I would like to start the Accordion closed, then when an event happens expand it and update the content of the Accordion.

I can do this with a Collapse object but I tried to use the is_open property on the Accordion and that did not seem to work.

Thanks @AnnMarieW
Yeah, I did create this “pattern” because at that time we didn’t have any accordion implementation;

@lanningb use dmc and it will be a lot easier to do the development of your project

Thank you.
I will go check it out!
I have been using the dbc. I can control the open and closed state using the Collapse object through callbacks.
But It like having the visual aid of the icons there for the user to know they can click on the header.

I will play with the dmc tool and see if I can do it with that.