Trigger callback when a page loads in order to update all plots/inputs


#1

I have a Dash app with some pages where every plot I have gets its data from a hidden div.

The problem I have is that every time I load a page the plots doesn’t update their content until I trigger it using some imput element (like a radio button). I found a way to ensure that when the page loads it also populates the element I have problems with. To do so I create a dummy hidden div an use it as an input but it doesn’t seem an elegant way to do that.

I think that the proper way to implement that should be an event that is triggered when the page is changed and the pass it to the callback. But after a lot of trial and research I haven’t been able to do it.

Example to show the problem

In order to make my problem more clear I used the code from the multipage tutorial (https://dash.plot.ly/urls) to have an easy example to show.

The files I used are:
– app.py
– index.py
– apps/app1.py
– apps/app2.py

with the following code:

app.py

import dash
import os

app = dash.Dash()
server = app.server
app.config.supress_callback_exceptions = True

index.py

from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

from app import app
from apps import app1, app2

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(list("ABC"), id="data", style={"display":"none"}),
    html.Div(id='page-content')
])


@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/apps/app1':
         return app1.layout
    elif pathname == '/apps/app2':
         return app2.layout
    else:
        return '404'

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

apps/app1.py

from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc

from app import app

layout = html.Div([
    html.H3('App 1'),
    dcc.Link('Go to App 2', href='/apps/app2'),
    dcc.Dropdown(id='app-1-dropdown'),
    html.Div(id='dummy_div'),
])

@app.callback(
    Output('app-1-dropdown', 'options'),
    [Input('data', 'children')])
def update_dropdown(options):
    return [{"label": x, "value": x} for x in options]

apps/app2.py

from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc

from app import app

layout = html.Div([
    html.H3('App 2'),
    dcc.Link('Go to App 1', href='/apps/app1'),
    dcc.Dropdown(id='app-2-dropdown'),
    html.Div(id='dummy_div'),
])

@app.callback(
    Output('app-2-dropdown', 'options'),
    [Input('data', 'children'),
     Input('dummy_div', 'children')]) #This is what ensures that the dropdown loads its options
def update_dropdown(options, aux):
    return [{"label": x, "value": x} for x in options]

In this example when you open App1 the dropdown it don’t have the options defined. But App2 it does have them due to the Input('dummy_div', 'children').

Any suggestions on how to implement that in a more elegant way?

Thanks in advance!


#2

Thanks for the workaround, I also had this issue but mine was loading a graph with DataTables. I used your solution and it worked like a charm. It does look messy but it works. If I find a cleaner solution I will make sure to let you know.


#3

Hi,

Assuming that your data Div doesn’t change after page load you do not need any callbacks in app1 and app2 to render your dropdowns. Instead you should make your layout in app1 and app2 into a function that gets passed the data by index.py like this:

Index.py

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

from app import app
from apps import app1, app2

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(list("ABC"), id="data", style={"display":"none"}),
    html.Div(id='page-content')
])


@app.callback(Output('page-content', 'children'),
             [Input('url', 'pathname')],
             [State('data', 'children')])
def display_page(pathname, data):
    if pathname == '/apps/app1':
         return app1.layout(data)
    elif pathname == '/apps/app2':
         return app2.layout(data)
    else:
        return '404'

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

app1.py

from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc

from app import app
def layout(options):
    return html.Div([
        html.H3('App 1'),
        dcc.Link('Go to App 2', href='/apps/app2'),
        dcc.Dropdown(id='app-1-dropdown', options=[{"label": x, "value": x} for x in options]),
    ])

app2.py

from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc

from app import app

def layout(options):
    return html.Div([
        html.H3('App 2'),
        dcc.Link('Go to App 1', href='/apps/app1'),
        dcc.Dropdown(id='app-2-dropdown', options=[{"label": x, "value": x} for x in options]),
    ])

If your data Div does change via some other Callback (time interval or something) then as you originally had it is fine. Also if the data doesn’t change, why have it in a hidden Div at all? Why not have a file on the server and include it in the layout by opening/importing it?