Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single Page App ("SPA") URL / Multi Page Support - Proposal and Development Preview #77

Closed
chriddyp opened this issue Jul 10, 2017 · 6 comments

Comments

@chriddyp
Copy link
Member

Hello Dash Community!

I just published 2 new components under the pre-release channel that enable multi-page support.

  • dash_core_components.Location component. Location represents the URL bar in the browser. The pathname property updates the pathname of the browser, refreshing the page if refresh=True.
  • dash_core_components.Link component. Link is like html.A except that it updates the Location's pathname directly, without refreshing the page (refresh=False). Like html.A, it takes href as a component. It doesn't render any markup itself, it just adds an "click" handler to whatever children were passed into it.

With these two components, we can make single-page apps with multiple URLs in Dash. A "single-page app" means that the pages update without doing an entire page refresh.

Here is a really eximple example:

# pip install dash_core_components==0.5.3rc1

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

app = dash.Dash()

app.layout = html.Div([
    # This "header" will persist across pages
    html.H2('Multi Page Dash App'),

    # Each "page" will modify this element
    html.Div(id='content-container'),

    # This Location component represents the URL bar
    dcc.Location(id='url', refresh=False)
], className="container")

@app.callback(
    Output('content-container', 'children'),
    [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/':
        return html.Div([
            html.Div('You are on the index page.'),

            # the dcc.Link component updates the `Location` pathname
            # without refreshing the page
            dcc.Link(html.A('Go to page 2 without refreshing!'), href="/page-2"),
            html.Hr(),
            html.A('Go to page 2 but refresh the page', href="/page-2")
        ])
    elif pathname == '/page-2':
        return html.Div([
            html.H4('Welcome to Page 2'),
            dcc.Link(html.A('Go back home'), href="/"),
        ])
    else:
        return html.Div('I guess this is like a 404 - no content available')

app.css.append_css({"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})

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

You can try this out with

$ pip install dash_core_components==0.5.3rc1

I'll keep this in the prerelease channel for the next week or so before adding it to 0.5.3 and including it in the documentation.

I think this is a pretty good solution to handling multiple URLs for now.

Happy to field any feedback on this :)

@chriddyp
Copy link
Member Author

Here's what that example looks like. Note how fast the page updates are - the browser is not refreshing, it's just updating the content through a single API call to the app.callback.

@ned2
Copy link
Contributor

ned2 commented Jul 14, 2017

This seems promising :) Some feedback... I just tried this out in the context of what is going to be a reasonable sized collection of separate dashboards. The difficulty I'm having incorporating this approach into my current code is getting all the callbacks to be applied to the layout fragment that corresponds to each distinct route.

For example, in your above code, how would you apply callbacks to elements in the layout that display_path returns? The tricky aspect seems to be that layout has to be registered before a callback can be applied to any contained elements.

It's possible to have at least one initial complete layout registered along with callbacks, however if a page load overwrites this based on a particular route pattern, then you wind up with the initial layout being rendered briefly then being updated with the route corresponding to the URL. (also, it the same problem of applying callbacks still applies to all routes other than the one initial one).

Perhaps I'm missing an obvious way around this problem though?

@ned2
Copy link
Contributor

ned2 commented Jul 14, 2017

Ah, perhaps it's as simple as adding app.config.supress_callback_exceptions=True to my aDash app? This seems to work now. Is there any potential gotchas of having callbacks applied to as of yet undefined elements aside from missing out on validation of IDs etc?

@chriddyp
Copy link
Member Author

Is there any potential gotchas of having callbacks applied to as of yet undefined elements aside from missing out on validation of IDs etc?

The main gotcha is that you have to define all of the callbacks upfront and that you have to set app.config.supress_callback_exceptions=True. I use this strategy to render the multi-page Dash Docs (source: https://github.com/plotly/dash-docs, live: https://plot.ly/dash).

@chriddyp
Copy link
Member Author

I have merged this into dash-core-components and updated dash-docs to use this pattern. A tutorial on this is now at https://plot.ly/dash/urls :)

@ned2
Copy link
Contributor

ned2 commented Jul 22, 2017

Great, thanks heaps for that @chriddyp.

byronz pushed a commit that referenced this issue Apr 23, 2019
HammadTheOne pushed a commit to HammadTheOne/dash that referenced this issue May 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants