Callbacks on <a> links

Can callbacks be triggered by clicks on a html.A link rather than a html.Button? The <a> tag is a far more flexible layout element.

Currently I use the n_clicks property of buttons to implement app logic:

@app.callback(
    Output('trend-interval-slider', 'value'),
    [
        Input('trend-erase-button', 'n_clicks')
    ],
)
def reset_trend_slider(n_clicks):
    return 1

Is it possible to use links instead?

1 Like

Sure, just give it an id and use either n_clicks or n_clicks_timestamp at the prop for the input trigger.

For reference, you can always look up the components on gh :slightly_smiling_face:

I think it depends on what you are doing. I have an app were instead of having a lot of inputs I use dcc.Link components to change the url (which in most cases would be equivalent to html.A components if you are not referring to a page outside the app), and then callbacks listening to the url to trigger different actions. I used this to avoid having to add a lot of checks of which button, dropdown, or checkbox was selected last.

Why do you want to use links instead of a button?

@rafaelc Could you point to some example code for “callbacks listening to the url to trigger different actions”?

Why do you want to use links instead of a button?

I want navigation in the app based on links rather than buttons or tabs. For example, I want to switch between different “screens” of the app by clicking a link in the navigation bar. See also:

I can’t share the app code because it’s for work, but in general it should be something like this:

First, you need to have a dcc.Location (I’ll use an id='url' here) in your base layout, and a div where you’ll include your dynamic contents (id='app-body'). Then you just use the pathname of the url component as the input to your app’s body and also the output of your dropdowns and buttons.

    dash.dependencies.Output('app-body', 'children'),
    [dash.dependencies.Input('url', 'pathname')])
def display_page_contents(pathname):
    # Logic to extract the parameters you need from the pathname. This could vary if you are using a multi-page app, for example
    args = pathname.split('/')[-1]
    
    if args = 'ABC':
        page_contents = html.Div([something ...])
    elif args = 'XYZ':
        page_contents = html.Div([something else...])
    return page_contents

Then you could have a dropdown for example with id ‘my-dropdown’ and pass the value to the url:

@app.callback(dash.dependencies.Output('url', 'pathname'),
              [dash.dependencies.Input('my-dropdown', 'value')])
def change_url(value):
   return value

Or a button that does the same:

@app.callback(
    app.callback(dash.dependencies.Output('url', 'pathname'),
    [dash.dependencies.Input('home-button', 'n_clicks')])
def button_click(n_clicks):
    # When you click the button, change the end of the url to 'ABC'
    return 'ABC'

Furthermore, in your app you could have dcc.Link components that change the pathname to href="/ABC" or href="/XYC", which would trigger the changes in app-body.

Hope this helps!

Thanks. This helped a lot with structuring the app.

One more thing: I have used the following to switch between different screens of my app:

@app.callback(
    Output('aspect-area', 'children'),
    [
        Input('location', 'pathname')
    ]
)
def switch_aspect(pathname):
    if pathname == "/":
        return [home_aspect]
    elif pathname == "/group":
        return [group_aspect]
    elif pathname == "/trend":
        return [trend_aspect]
    elif pathname == "/combi":
        return [combi_aspect]
    elif pathname == "/search":
        return [search_aspect]
    else:
        pass

This works fine if the only thing I want to do with location is to switch between the screens. But what if I want to use URIs to control other aspects of the app. That is, have many different outputs that are nested within the screens have a callback using location as input. It’s not clear to me how to make it work.

To address you question as stated, it is perfectly possible to add location as input to as many outputs as you like (nested in screens or not). However, if the whole page is updated anyway, it might be better to add location as state for the nested outputs.

I wish I had a code example for this. I am thinking about a REST-style interface to the functionality of my app, something like

http://example.com/view_widgets
http://example.com/create_new_widget?name=Widgetizer
http://example.com/update_widget?id=123&name=Foo
http://example.com/delete_widget?id=123

That would be more flexible than working with buttons and fields.

Is this a good pattern for Dash apps? It does not seem easy to make it work.

There’s no built-in code for handling GET params, but you could parse the URI yourself and dispatch to the relevant function along with extracted arguments. I’m hoping that we’ll see a better experience around this part of Dash down the line.

1 Like

Parsing the URIs is the easy part, what I am struggling with is having multiple callbacks dependent on the URI. One that switches the content of a container area, and others that control content nested within.

Could it all be handled by a single callback though?

It sounds like you want to dispatch to different callbacks based on params of the URI. Why not do all that dispatching to regular Python functions and have the complete page assembled within the one Location callback?

If I’m misunderstanding what you mean, could you provide a more concrete example?

if you really wanted to use multiple callbcks you could have a second one listening to changes on the children prop of your container element (resulting in a cascading callback) however that will require two round trips to the server to handle.

1 Like

For a similar purpose i use a setup where i parse the URL into a “path”, i.e. the “http://example.com/create_new_widget” part, and a list of arguments, i.e. the “?name=Widgetizer” part. I then choose the page based on the path and pass the arguments to the page.

2 Likes

Possibly it could. Not ready yet for such a major architecture overhaul of my app, but I will think about it.