Manually triggering a callback, or manually changing a Input value to trigger it?

Hi guys,

I have a single page dash app that has a two tabs (Map and Status) and a callback function to render the tab based on the navigation selected and job site:

body = html.Div([
    navbar.build_navbar(data_processor.site_name_list,
                        data_processor.config_builder),
    dcc.Loading(children=[dbc.Container(
        children=[
            html.Div(id="main-area"),
        ]
    )], type='default', fullscreen=True),
    footer.build_footer()
], className="body"
)

@app.callback(
    Output("main-area", "children"),
    Input("navigation-tabs", "value"),
    Input("jobsite-dropdown", "value"))
    # Input('summary_map', 'clickData'))
def render_content(tab:str, job_site:str, map_click_data:dict=None):
    '''
        Renders the main area of the dashboard.  Returns a list of dcc html.Divs

        Parameters:
            tab - the name of the tab selected in the navbar
            job_site - the name of the job site
            map_click_data - unit data of the clicked unit on the summary map
    '''
  

    # MAP PAGE
    if tab == strings.TAB1_NAME:

        return build_map_page(data_processor, job_site, last_data_refresh_html)
    
    # STATUS PAGE
    elif tab == strings.TAB2_NAME:
        return build_statust_page(data_processor, job_site, last_data_refresh_html)

I would like to add some extra functionality so that a user can click on a marker on the map and go to a certain section on the status tab based on the marker clicked. I can get the marker name clicked using this callback:

# test callback when clicking on map
@app.callback(
    Output('click-data', 'children'),
    Input('summary_map', 'clickData'))
def display_click_data(clickData):
    if clickData is None:
        return None
    else:
        unit_name=clickData['points'][0]['customdata'][0]

        return html.Div(unit_name)

What i would like to know is how to render the status tab when the user clicks on the map? Can you manually call the render_content() callback function? Can you change the Input(“navigation-tabs”) value somehow to trigger this callback? or is there a simpler way im overlooking?

Ive tried including Input(‘summary_map’, ‘clickData’)) to the render_content() callback but i get this error ‘A nonexistent object was used in an Input of a Dash callback.’

Thanks for any suggestions, Precog

Hi @precog,

The error that you got has to do with the way the tab content is rendered (via a callback). Since “summary_map” is added to the layout just when the Map tab is on, you can’t use it directly in the same callback that renders the tab content (it is only defined when the map tab is selected).

One alternative would be to use a dcc.Store component defined outside the tabs that will hold the clickData information. In other words, just the test callback that you wrote with Output("store-id", "data") and returning whatever part of clickData you need… In this case, dcc.Store(id="store-id") is always present in the layout and can trigger updates in render_content if used as input. The last bit would be to use callback context to know if the callback was triggered by a change from nav or from a click in the map, then update the content accordingly to each case.

Let me know if this is helpful or you need some clarification.

1 Like

Thank so much for your reply. I had no idea about those dash functionalities. I read up and implemented your suggestions and that worked! The only issue I have is the selected tab highlight is out of sync when user goes to the status page from clicking on a map marker as shown in the image attached. This image shows the status page but the tab is indicating we are on the Summary page

One other question, is there a way to manually clear the dcc.Store(id=“store-id”) value outside of the callback? I tried dcc.Store(id=“store-id”, clear_data=True) but that didn’t work

Thanks again for your help.

What did you use to write your navbar? dbc?

Used dash core components and dash html components.

‘’’

def build_navbar(site_list: list[str], config_builder: IConfigBuilder)->html.Header:

# dropdown_list = [{"label": site, "value": site} for site in site_name_list]

site_name_list = site_list

navbar = html.Header(

    children=[

        # Icon and title

        html.Div(

            className="dash-title-container",

            children=[

                html.Img(className="dash-icon", src=strings.GEOMATIX_LOGO, height="30px"),

                html.H1(className="dash-title", children=["GEOMATIX REPORTING DASHBOARD"]),

            ],

        ),

        #Job Site Dropdown

        html.Div(

            className="dropdown-select-jobsite-container",

            children=[

                html.Label(

                    className="control-label", children=[strings.DROPDOWN_JOB_SITE_LABEL]

                ),

                dcc.Dropdown(

                    id="jobsite-dropdown",

                    className="control-dropdown",

                    clearable=False,

                    # options=[{f'label: {site}, value: {site}' for site in site_name_list}],

                    options=[{"label": config_builder.get_site_display_name(site), "value": site} for site in site_name_list],

                    style={'cursor': 'pointer'},

                    # style={"border-color": "#008ede"},

                    value=site_name_list[0]

                ),

            ],

        ),

        # create tabs with buttons

        html.Nav(

            children=[

                dcc.Tabs(

                    id="navigation-tabs",

                    value=strings.TAB1_NAME,

                    children=[

                        dcc.Tab(

                            label=strings.TAB1_NAME,

                            value=strings.TAB1_NAME,

                            className="dash-tab",

                            selected_className="dash-tab-selected",

                        ),

                        dcc.Tab(

                            label=strings.TAB2_NAME,

                            value=strings.TAB2_NAME,

                            className="dash-tab",

                            selected_className="dash-tab-selected",

                        ),

                        dcc.Tab(

                            label=strings.TAB3_NAME,

                            value=strings.TAB3_NAME,

                            className="dash-tab",

                            selected_className="dash-tab-selected",

                        ),

                        dcc.Tab(

                            label=strings.TAB4_NAME,

                            value=strings.TAB4_NAME,

                            className="dash-tab",

                            selected_className="dash-tab-selected",

                        ),

                    ],

                ),

                       

            ]

        ),

    ]

)

return navbar

‘’’

I see… That’s bad, because I don’t think you can change the active tab other than changing value in dcc.Tabs(). Since this is already an Input in your callback, you wouldn’t be able to use as an Output…

You could consider switching from dcc.Tabs to Nav components from dash-bootstrap-components. I think they will provide you more flexibility, as you can change the active prop without interacting. You tabs already look like a Nav component anyways, given where they are located in the page…

SO how can you change the active prop without interacting with it?

Here is a tiny reproducible example of what I meant by changing without interacting:

from dash import Dash, html, Input, Output, State
import dash_bootstrap_components as dbc

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

navbar = dbc.Nav(
    [
        dbc.NavItem(dbc.NavLink("Active", id="nav1", active=True,)),
        dbc.NavItem(dbc.NavLink("Inactive", id="nav2")),
    ],
    pills=True
)

app.layout = html.Div(
    [
        navbar,
        dbc.Button(id="button", children="Click me"),
    ]
)

@app.callback(
    Output("nav1", "active"),
    Output("nav2", "active"),
    Input("button", "n_clicks"),
    State("nav1", "active"),
    prevent_initial_call=True,
)
def update_navbar(n_clicks, is_active):
    if is_active:
        return False, True
    else:
        return True, False

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

Hope this helps! :slight_smile:

Great…I’ve been updating children components but didn’t really know you could update other attributes!

1 Like