Multi-Page Apps - callbacks to other pages

So I have a simple multi-page app, similar to the user-guide example (https://dash.plot.ly/urls). Here’s a simplified version:

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
import time
from dash.dependencies import Input, Output, State

external_stylesheets = [dbc.themes.BOOTSTRAP, 'https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.config.suppress_callback_exceptions = True

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])


index_page = html.Div([
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])

page_1_layout = html.Div([
    html.H1('Page 1'),
    dcc.Dropdown(
        id='page-1-dropdown',
        options=[{'label': i, 'value': i} for i in ['LA', 'NYC', 'MTL']],
    ),
    html.Div(id='page-1-content'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
    html.Br(),
    dcc.Link('Go back to home', href='/'),
    dbc.Modal(
                [
                    dbc.ModalHeader("END OF COMPLEX OPERATIONS"),
                    dbc.ModalBody("Finally!"),
                ],
                id='end-modal',
                is_open=False,
            )
])


# Complex stuff that takes some time to complete and in the end shows an alert
@app.callback(
    [
    Output('page-1-content', 'children'),
    Output("end-modal", "is_open"),
    ],
    [Input('page-1-dropdown', 'value')],
    [State("end-modal", "is_open")]
)
def page_1_complex_calculation(value, is_open):
    
    # time consuming operations represented with some sleep time
    time.sleep(5)
    return result, not is_open


page_2_layout = html.Div([
    html.H1('Page 2'),
    dcc.RadioItems(
        id='page-2-radios',
        options=[{'label': i, 'value': i} for i in ['Orange', 'Blue', 'Red']],
        value='Orange'
    ),
    html.Div(id='page-2-content'),
    html.Br(),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go back to home', href='/')
])

page_3_layout = html.Div([
    'Some more stufff!!!!'
])
@app.callback(dash.dependencies.Output('page-2-content', 'children'),
              [dash.dependencies.Input('page-2-radios', 'value')])
def page_2_radios(value):
    return 'You have selected "{}"'.format(value)


# Update the index
@app.callback(dash.dependencies.Output('page-content', 'children'),
              [dash.dependencies.Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/page-1':
        return page_1_layout
    elif pathname == '/page-2':
        return page_2_layout
    elif pathname =='/page-3':
        return page_3_layout
    else:
        return index_page


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

In the example above, in page 1, the app does some complex stuff (here represented as time.sleep(5)) which take some time to complete. In the end of that complex routine, the callback displays an alert (using the Modal component from dash-bootstrap-components) to tell the user that the calculations are complete.

Now the problem is that the user might navigate to the other pages, say, page 2 or page 3, while this time consuming operations are running. When I simulate that situation, the alert that was fired in page 1 obviously does not show-up in the other pages, since the Modal is declared int the scope of page 1.

How can I create a callback that can operate outside of it’s original page context? Or is there some workaround for this problem?

Still can’t find a solution for this. Did anyone else managed to work it out?

have you tried using Tabs instead of pages? This way, your alert will be in-scope? Not sure Tabs will fit your app design but it’s the next best thing when wanting separation in your app w/o using pages.

Yes I did! But pages are more suitable for this situation, given the large number of pages that I’ll be using. I have one idea, which is to redirect the user to another page when that calculation is complete. Is it possible? For example, using flask.redirect? If so, I could put the alert in one extra “hidden” page and the user would always be redirected for that “hidden” page, no matter where he is in the app.

Instead of redirecting the user to another page when the calcs are complete, why not return them to the page assoc. with those calcuations? I don’t have much experience w/ Flask to know if flask.redirect will work as you described.

Yeah, that’s probably better :slight_smile:

Just put the modal in app.layout, not a page layout. This way it is always part of the layout since you are just swapping the page-content div.

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
import time
from dash.dependencies import Input, Output, State

external_stylesheets = [dbc.themes.BOOTSTRAP, 'https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.config.suppress_callback_exceptions = True

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    dbc.Modal(
        [
            dbc.ModalHeader("END OF COMPLEX OPERATIONS"),
            dbc.ModalBody("Finally!"),
        ],
        id='end-modal',
        is_open=False,
    ),
    html.Div(id='page-content'),
])

index_page = html.Div([
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])

page_1_layout = html.Div([
    html.H1('Page 1'),
    dcc.Dropdown(
        id='page-1-dropdown',
        options=[{'label': i, 'value': i} for i in ['LA', 'NYC', 'MTL']],
    ),
    html.Div(id='page-1-content'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
    html.Br(),
    dcc.Link('Go back to home', href='/'),

])


# Complex stuff that takes some time to complete and in the end shows an alert
@app.callback(
    Output("end-modal", "is_open"),
    [Input('page-1-dropdown', 'value')],
    [State("end-modal", "is_open")]
)
def page_1_complex_calculation(value, is_open):
    # time consuming operations represented with some sleep time
    time.sleep(5)
    return True


page_2_layout = html.Div([
    html.H1('Page 2'),
    dcc.RadioItems(
        id='page-2-radios',
        options=[{'label': i, 'value': i} for i in ['Orange', 'Blue', 'Red']],
        value='Orange'
    ),
    html.Div(id='page-2-content'),
    html.Br(),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go back to home', href='/')
])

page_3_layout = html.Div([
    'Some more stufff!!!!'
])


@app.callback(dash.dependencies.Output('page-2-content', 'children'),
              [dash.dependencies.Input('page-2-radios', 'value')])
def page_2_radios(value):
    return 'You have selected "{}"'.format(value)


# Update the index
@app.callback(dash.dependencies.Output('page-content', 'children'),
              [dash.dependencies.Input('url', 'pathname')])
def display_page(pathname):


    if pathname == '/page-1':
        return page_1_layout
    elif pathname == '/page-2':
        return page_2_layout
    elif pathname == '/page-3':
        return page_3_layout
    else:
        return index_page


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

Well that did work thanks @sjtrny! My problem was that my page was a bit more complex and the modal had a few more dependencies that eventually made it not work in that way. However, I managed to work it out, thanks