✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🐇 Announcing Dash VTK for 3d simulation graphics. Check out the March webinar.

Updating html.Details `open` property/replacing a Details component doesn't behave as expected

I’m trying to update a Details component, and when I update it I want it to “close”. Assigning it’s open property to ‘False’ doesn’t work as expected, also replacing it with another Details with open = False also doesn’t seem to work as expected (nor just replacing the `Details without setting the open property).

Below is a toy dash for seeing the behaviour. The first three details-button pairs behave seemingly such that their first press works and alters the open state of the component and then never again after that -> not updating the Details components as I would expect. Then the final pair (4) adds the curve ball that it seems that all presses of the button will have an affect on the component -> this is how I would expect it to behave, but it is not in keeping with examples 1-3.

My aim is to replace a Details component with another and for it to be closed on update; in reality I’m doing this will a series of nested Details components replacing a Div’s children attribute and I’m observing that any level of the nested Details object which I have previously opened stay open on update of the Div’s children.

Any thoughts?

import dash
from dash.dependencies import Input, Output, State
import dash_html_components as html
app = dash.Dash()
app.layout = html.Div([
    html.Details([
        html.Summary('Details'),
        html.P('Content')
    ],
    #open=True,
    id='details'),
    html.Button('Close details', id='button'),

    html.Div([
        html.Details([
            html.Summary('Details2'),
            html.P('Content2')
        ]),
    ],id='div2'),
    html.Button('Replace 2 with new details', id='button2'),

    html.Div([
        html.Details([
            html.Summary('Details3'),
            html.P('Content3')
        ]),
    ],id='div3'),
    html.Button('Replace 3 with new details set `open = False`', id='button3'),

    html.Details([
        html.Summary('Details4'),
        html.P('Content4')
    ],id='details4'),
    html.Button('Open/Close 4', id='button4'),
])
@app.callback(
    Output('details', 'open'),
    [
        Input('button', 'n_clicks')
    ],
    [
        State('details', 'open')
    ])
def close_details(n_clicks, d_open):
    if n_clicks:
        print('Close 1')
        return False
    else:
        return d_open
@app.callback(
    Output('div2', 'children'),
    [
        Input('button2', 'n_clicks')
    ],
    [
        State('div2', 'children')
    ])
def new_details(n_clicks, children):
    if n_clicks:
        print('New 2')
        details = html.Details([
            html.Summary('Details2New'),
            html.P('Content2New')
        ])
        return [details]
    else:
        return children
@app.callback(
    Output('div3', 'children'),
    [
        Input('button3', 'n_clicks')
    ],
    [
        State('div3', 'children')
    ])
def new_details_open_false(n_clicks, children):
    if n_clicks:
        print('New 3')
        details = html.Details([
            html.Summary('Details3New'), #removing the 'New' doesn't change behavior.
            html.P('Content3New')
        ],open=False)
        return [details]
    else:
        return children
@app.callback(
    Output('details4', 'open'),
    [
        Input('button4', 'n_clicks')
    ],
    [
        State('details4', 'open')
    ])
def open_close_4(n_clicks, d_open):
    if n_clicks:
        if n_clicks % 2 == 0:
            print('open 4')
            return True
        else:
            print('close 4')
            return False
    else:
        return d_open
if __name__ == '__main__':
    app.run_server(debug=True)
1 Like

Hi,
Was there ever a solution found for this? I am using a callback to revert a html detail back to closed (open = False) but it doesn’t appear to do anything:

Example:

    html.Details([
        html.Summary('Show Description'),
        html.Div(children=["My Description Text"], className='desc',
                 id='my-description')
    ],
        id="desc-dropdown",
        open=False
    ), 

If I open/expand the dropdown on the web page then have something fire a callback along the lines of (this simplified callback):

@app.callback(
    [Output('desc-dropdown', 'open')],
    [Input('some-radio', 'value')]
)
def update_page(selected_radio):
        return False

…the dropdown stays open.

Actually, I just replaced return False with return None, and that seems to do the trick.

1 Like

In my case, the value needs to be toggled between None and False to have the desired effect. When open is initially set to False, setting it to None works once and then stops. This snippet works to close it every time:

@app.callback(
    [Output('desc-dropdown', 'open')],
    [Input('some-radio', 'value')],
    [State('desc-dropdown', 'open')],
)
def update_page(selected_radio, is_open):
        return False if is_open is None else None

Yes, this is strange behavior. And there is more. Letting the browser perform the toggling of the twisty in html.Detail does not change the open attribute of the object, it stays at the value it was last set to in Python. In the example above, the value of open will always be False or None, even if the twisty is opened by the browser upon being clicked.

If you encounter unexpected behavior, keep this in mind and examine your callback attributes using dash.callback_context.triggered, .inputs., and .states. If you are using the built-in webserver during development, you can even print these within the callback and they will appear in the terminal within which the server is running.