Show spinner in dbc.Button() via client-side callback

Hey there,

I just wanted to quickly drop an example here for how to show a spinner once the button has been clicked. Pretty much the same as described here for the dmc.Button()

import dash
from dash import clientside_callback, html, Input, Output, MATCH
import dash_bootstrap_components as dbc
import json
import time

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

app.layout = html.Div(
    [
        dbc.Button(
            id={'type': 'btn', 'index': 0},
            children='Load data'
        ),
        html.Div(id={'type': 'container', 'index': 0})
    ]
)

# callback for showing a spinner within dbc.Button()
clientside_callback(
    """
    function (click) {
        return [""" + json.dumps(dbc.Spinner(size='sm').to_plotly_json()) + """, " Loading..."]
    }
    """,
    Output({'type': 'btn', 'index': MATCH}, 'children'),
    Input({'type': 'btn', 'index': MATCH}, 'n_clicks'),
    prevent_initial_call=True
)


# callback for showing a message and setting back the dbc.Button() to the initial state
@app.callback(
    Output({'type': 'container', 'index': MATCH}, 'children'),
    Output({'type': 'btn', 'index': MATCH}, 'children', allow_duplicate=True),
    Input({'type': 'btn', 'index': MATCH}, 'children'),
    prevent_initial_call=True
)
def show_message(_):
    time.sleep(3)
    return 'Data has been loaded', 'Load data'


if __name__ == '__main__':
    app.run(debug=True)

mred dupout cscb

4 Likes

Very helpful example, @AIMPED . Thanks for sharing. I achieved a similar result with two very long callbacks. Your solution is a lot neater. :clap:

1 Like

Nice one @AIMPED

Here is another way to do it through css and no chained callback:

css sheet:

.load-button[data-dash-is-loading="true"] {
    filter: brightness(.8);
    pointer-events: none;
    cursor: not-allowed;
    font-size: 0px;
}

.load-button[data-dash-is-loading="true"]:after {
    content: "Loading...";
    font-size: initial;
    display: inline-block;
    margin-left: 3px
}

.load-button[data-dash-is-loading="true"]:before {
    content: "\f110";
    font-size: initial;
    font-family: "Font Awesome 5 Free";
    font-weight: 900;
    -webkit-animation:spin 1.4s linear infinite;
    -moz-animation:spin 1.4s linear infinite;
    animation:spin 1.4s linear infinite;
    display: inline-block;
}

@-moz-keyframes spin {
    100% { -moz-transform: rotate(360deg); }
}
@-webkit-keyframes spin {
    100% { -webkit-transform: rotate(360deg); }
}
@keyframes spin {
    100% {
        -webkit-transform: rotate(360deg);
        transform:rotate(360deg);
    }
}

app.py

import dash
from dash import clientside_callback, html, Input, Output, MATCH, dcc, no_update
import dash_bootstrap_components as dbc
import json
import time

app = dash.Dash(
    __name__,
    external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.FONT_AWESOME]
)

app.layout = html.Div(
    [
        dbc.Button(
            id={'type': 'btn', 'index': 0},
            children='Load data',
            className='load-button'
        ),
        html.Div(id={'type': 'container', 'index': 0})
    ]
)

# callback for showing a message and setting back the dbc.Button() to the initial state
@app.callback(
    Output({'type': 'container', 'index': MATCH}, 'children'),
    Output({'type': 'btn', 'index': MATCH}, 'children'),
    Input({'type': 'btn', 'index': MATCH}, 'n_clicks'),
    prevent_initial_call=True
)
def show_message(_):
    time.sleep(3)
    return 'Data has been loaded', no_update


if __name__ == '__main__':
    app.run(debug=True)
3 Likes

Nice @jinnyzor!

I have to learn this css magic!

1 Like

Hello all,

@AIMPED @jinnyzor, thanks for the great solutions. Was looking quite a while to achieve this. Your posts gave me inspiration to look some more and combine a feature of the Dash Mantine components (LoadingOverlay), to keep it simple.

By setting the LoadingOverlay and using the parameters:

overlayColor=“rgba(0, 0, 0, 0.8)”
overlayBlur=10
zIndex=1000

The desired button loading is displayed as long as the callback takes to execute. My button in this example is dark, adjust the colors to your own needs. Hope it helps someone in their project!

Happy coding

import dash
from dash import html, Input, Output, no_update
import dash_bootstrap_components as dbc
import dash_mantine_components as dmc

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

app.layout = html.Div(
                        [
                            dmc.LoadingOverlay(
                                html.Div(id='loading-customize-output',
                                        children=[
                                            dbc.Button(children=["Click Me!"],
                                                id="customize-button",
                                                n_clicks=0,
                                                class_name='d-grid gap-2 col-12 mx-auto btn btn-dark',
                                            ),
                                ]),
                                    loaderProps={"variant": "dots", "color": "white"},
                                    overlayColor="rgba(0, 0, 0, 0.8)",
                                    overlayBlur=10,
                                    zIndex=1000,
                            )
                        ]
                        )
@app.callback(
        Output("loading-customize-output", "children"),
        Input("customize-button", "n_clicks"),
)
def func(n_clicks):
    time.sleep(4)
    return no_update


if __name__ == '__main__':
    app.run(debug=True)
1 Like