Card with collapsible contents

I really like that in R shiny bs4dash cards have a button to minimize them so I made it for python dash. I thought I’d share to see if others had a better approach or just wanted to use it as-is.

There are 3 things that make this work (really two to make it function), the last is just external style.

  1. These two functions:
def myCard_collapse(title, children, headsize=None, classcard=None, classbody=None):
    headsize=4 if headsize is None else headsize
    sameid=str(uuid.uuid4())
    if not isinstance(children, list):
        children=[children]
    newchildren=[
        html.Button(className="btn btn-tool btn-sm", 
                    children=html.I(className="fa fa-minus", id={'type':'collapseicon','id':sameid}), 
                    style={'position':'absolute','top':'0','right':'0'},
        id={'type':'collapsebutt','id':sameid}),
        dbc.Collapse(children, is_open=True, id={"type":'collapsewin','id':sameid})
    ]
    if title is None:
        cardbody=newchildren
    else:
        cardbody=[dynH(title, headsize, className="card-title")] + newchildren
    
    return dbc.Card(dbc.CardBody(cardbody, className=classbody), className=classcard)

def dynH(children, headsize, className=None):
    if headsize==1:
        return html.H1(children, className=className)
    elif headsize==2:
        return html.H2(children, className=className)
    elif headsize==3:
        return html.H3(children, className=className)
    elif headsize==4:
        return html.H4(children, className=className)
    elif headsize==5:
        return html.H5(children, className=className)
    elif headsize==6:
        return html.H6(children, className=className)
    else:
        raise ValueError("headsize must be 1-6")

of course, you don’t really need the second if you change the first function to not use it but up to the user.

  1. These two clientside callbacks (could probably be just one but I made it two)
    app.clientside_callback("""
                            function(clicks, is_open) {
                                if (clicks>0) {
                                    return !is_open;
                                } else {
                                    throw window.dash_clientside.PreventUpdate;        
                                }
                            }""",
                            Output({"type":'collapsewin','id':MATCH},'is_open'),
                            Input({'type':'collapsebutt','id':MATCH}, 'n_clicks'),
                            State({"type":'collapsewin','id':MATCH},'is_open'))
    
    app.clientside_callback("""
                            function(is_open) {
                                if (is_open==true) {
                                    return "fa fa-minus";
                                } else {
                                    return "fa fa-plus";
                                    }
                                }""",
                                Output({'type':'collapseicon','id':MATCH}, 'className'),
                                Input({"type":'collapsewin','id':MATCH},'is_open')
    )
  1. adding font-awesome style sheet to external_stylesheets
external_stylesheets=[
            dbc.themes.BOOTSTRAP, dbc.icons.BOOTSTRAP, 
            "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css",
        ]

If you want to use default buttons then you can skip this but I like the - + icons.

Obviously, I’m a terrible for not having docstrings or using camel case.