AnnMarieW / dash-multi-page-app-demos

Minimal examples of multi-page apps using Dash Pages
229 stars 53 forks source link

Dash pages multi-page app demos

This repo contains minimal examples of multi-page apps using the Pages feature available in dash>=2.5.1

See the Dash Documentation Multi-Page Apps and URL Support

__:movie_camera: Don't miss the video tutorials:__

I hope these examples help you get started exploring all the cool features in Pages. If you find this project helpful, please consider giving it a :star:


Example Apps

The easiest way to begin is by cloning this repository and running the examples on your local machine. Below is a brief description of each app.

If you are using a Jupyter Notebook:

Other tutorials or examples using pages:

  1. Adding a Blog to your Dash app. See this Dash Community Forum post. It describes how to do this and includes this repo from @bradley-erickson.

  2. See the Dash Webb Compare app live. This app shows the first images from the James Webb Space Telescope. Compare before and after images of Hubble vs Webb. The Github repo has 2 versions of the app using pages.

    • app_pages.py - Creates an multi-page app without using the pages folder.
    • app_pages_no_assets.py - This multi-page app uses images that are hosted on GitHub so it doesn't use either the pages or the assets folder.

Tips and Tricks

  1. Pretty print dash.page_registry - with the print_registry() function from dash-labs
  2. How to use dcc.Link in Markdown - for high performance page navigation from a link in a dcc.Markdown component.
  3. Avoiding duplicate ids - Strategies for handling ids in a large multi-page app.
  4. Display loading screen when page_container is loading - Shows how to make the overall loading screen only display when there is a change to the _pages_content that involves a layout being changed and not changes within the layout.
  5. Preventing Query String Errors



Example Apps

1. multi_page_basics/

This folder has a minimal overview of the basic pages features, including:

The image below :point_down: is from the path_variables page. Note that asset "inventory" and department "branch-1001" are passed from the pathname to the layout function and are displayed on the page.

basics




2. multi_page_pathname_prefix/

This example shows how to use the relative_path attribute in dash.page_registry in deployment environments that use a pathname prefix. It also shows use of dash.get_asset_url() to get the correct path to the assets folder from a file in the pages folder.

Note the /app1/ pathname prefix in the url :point_down:

pathname_prefix




3. [multi_page_cache/]()

Example removed - please see #11 multi_page_store and #4 multi_page_cach_background_callbacks.



4. multi_page_cache_background_callbacks

This example shows how to use caching and background callbacks in a multi-page app. The examples in the dash docs needed to be modified to make it possible to switch pages while background callbacks are running.



5. multi_page_example1/

This example shows a small app with three pages with callbacks. Each page displays a figure. It uses dash-bootstrap-components with dbc.DropdownMenu to display the links in a navbar.

Example1




Authentication - Basic

5a. multi_page_dash_auth

This example is the multi_page_example1 app with HTTP Basic Auth from thedash-auth` package. Basic Auth section of the dash docs.

Authentication - using Flask

6. multi_page_flask_login/

You will find two similar examples.

  1. multi_page_flask_login/ - original example
  2. multi_page_flask_login2/ - the new and improved version contributed by @jinnyzor. See this Dash Community Forum post for more information

flask__login

Authentication with Flask and OAuth

Here is a great example by @leberber

I have created an example Dash application that integrates with Flask for user authentication, including support for OAuth with Google. The application uses the Mantine library for UI components and supports user registration, login, and logout functionality. Additionally, it includes a theme switcher and sidebar toggle implemented via client-side callbacks

  • See this Dash community forum post for more information
  • See the code in Github
  • See the live example Please allow for up to a minute for the app to comp up.

For other authentication options see:




7. multi_page_layout_functions/

This app demonstrates how to create a sub-topics sidebar that is only used in certain pages. It shows how to use functions to access the dash.page_registry from within the pages folder after it's finished building. For more details see the dash docs: https://dash.plotly.com/urls#dash-page-registry

It also shows how arbitrary data added to the dash_page_registry can be used. In this app, we add top_nav=True on the three pages we want to include in the top nav bar. Then we create the nav links like this:

    dbc.Nav(
        [
            dbc.NavLink(page["name"], href=page["path"])
            for page in dash.page_registry.values()
            if page.get("top_nav")
        ],
    ),

pages-side-nav-funct




7b multi_page_path_variables

Here's how to make an example like the one above, but using path variables.
This example also shows how to add a different title and meta tag descriptions for each of the pages specified in the path variable.

For more information, see this forum post on how to Contain a Dash Page Under a Parent Page.

multi-page-demo-path-variables




8. multi_page_meta_tags/

This app shows more details on how the images are added to the meta tags. See also the Dash Documentation: https://dash.plotly.com/urls#meta-tags




9. multi_page_nested_folder/

For more info, please see the Dash Documentation: https://dash.plotly.com/urls#nested-pages This app demonstrates the case where you have nested folders with pages folder, like in the following:

- app.py 
- pages
    - chapter1       
       |-- page1.py
       |-- page2.py
    - chapter2       
       |-- page1.py
       |-- page2.py
    - home.py

It also demos how to add arbitrary data to the page_registry. It adds icons to the page_registry which are used when creating the links.

This app uses dash-mantine-components and dash-iconify libraries. nested_folders




10. multi_page_query_strings/

This app demonstrates passing variables to a page using query strings. For more information see the Dash Documentation: https://dash.plotly.com/urls#query-strings. You will also see how to use a dcc.Link within a dcc.Markdown

query_strings




11. multi_page_store/

This app shows how to share data between callbacks on different pages using a dcc.Store component.

pages-share-data-between-pages




12. multi_page_table_links/

This app uses dcc.Linkin the cells of a Dash AG Grid to navigate to a new page without refreshing the page.

grid links




13. multi_page_sync_components/

These examples show how to synchronize component values between pages.

You will find two example:

  1. multi_page_sync_components/ is a simple example which uses the same component on each page and sets persistence=True Thanks @nopria for the example!
  2. multi_page_sync_components2/In some cases, the simple example won't work (ie component values updated in callbacks). Version 2 uses a dcc.Store component to sync the component values. It required dash>=2.9.2 to allow updating the dcc.Store from multiple callbacks on different pages.

sync




14. multi_page_theme_switch/

This example demonstrate a light and dark theme switch component from the dash-bootstrap-templates library. See a live demo at Dash Bootstrap Theme Explorer The Theme Explorer app is also made with pages :tada:

For Dash Enterprise Customers, see: Dash Design Kit

theme_switch




Navigation in a callback

With Dash Pages, the routing callback is under-the-hood, which reduces the amount of boilderplate code you need to write. The best way to navigate is to use components such as the dcc.Link or dbc.Button. When the user clicks on these links, it will navigate to the new page without refreshing the page, making the navigation very fast. And the best part? No callback required! :tada:

This works well when you have static links. However, at times, you may want to navigate based on an input field, dropdown, or clicking on a figure etc. There are two options:

1) Update href of dcc.Location in a callback. Not recommended in Dash<2.9.2 because it refreshes the page. 2) Update the link in a callback. Best practice!

:tada: New in dash 2.9.2 dcc.Location(refresh="callback-nav") - navigate without refreshing the page. See examples below




15. multi_page_update_url_in_callback/

15b.multi_page_update_url_in_callback_V292/

See two versions of the same app. It shows both ways to navigate in a callback - by updating dcc.Location and by updating links. The V2.9.2 version uses the "callback-nav" option in dcc.Location so that the page does not refresh.

callback-nav

For more information see this [community forum post.]()

Here are more examples. This one (best practice) is to update a link when a user clicks on a figure:




16. multi_page_update_url_from_figure/

fight-status




16b. multi_page_update_url_from_figure_V292/

This option is available with dash>=2.9.2. It uses dcc.Location(refresh="callback-nav") to navigate without refreshing the page. img

callback-nav-fig-292

There are some known issues with dcc.Location. Here are some workarounds to avoid things like the browser crashing or the back button not working:


app.layout = html.Div(
    [
        dcc.Location(id="url", refresh="callback-nav"),
        navbar, page_container,
    ], 
)



17. multi_page_no_pages_folder

This is an example of how to create a multi-page app without using the pages folder.

Use cases:

  1. Jupyter Notebooks: Use this method when using a Jupyter Notebook and you would like to run the code as a cell. To run this example in a Jupyter Notebook, copy the content of the app.py file and paste it into a notebook cell.
  2. Accessing Pages features in a single page app. For more information and examples, see this forum post



Tips and Tricks

1. print_registry() from dash-labs>-1.1.0

When debugging a pages app, it's very helpful to inspect the content of the dash.page_registry.

print_registry() is a handy utility that pretty-prints all or part of the dash.page_registry dict.

Examples for print_registry()


from dash import Dash, html, register_page

# must use dash-labs>=1.1.0
from dash_labs import print_registry

app = Dash(__name__, use_pages=True)

register_page("another_home", layout=html.Div("We're home!"), path="/")

print_registry()

.... rest of your app

Will print to the console:

{'another_home': {'module': 'another_home',
                  'supplied_path': '/',
                  'path_template': None,
                  'path': '/',
                  'supplied_name': None,
                  'name': 'Another home',
                  'supplied_title': None,
                  'title': 'Another home',
                  'description': '',
                  'order': 0,
                  'supplied_order': None,
                  'supplied_layout': Div("We're home!"),
                  'image': None,
                  'supplied_image': None,
                  'image_url': None,
                  'redirect_from': None,
                  'layout': Div("We're home!")}}

Reference

print_registry(modules='ALL', exclude=None, include='ALL')

Params:

Examples:




2. :tada: Use dcc.Link in dcc.Markdown

Did you know it's possible to use dcc.Link in dcc.Markdown? The advantage of using dcc.Link to navigate between pages of a multi-page app is that when you click on the link it updates the pathname without refreshing the page -- which makes browsing really fast. :rocket:

Here's how:

dcc.Markdown( "This is text <dccLink href='page1/news' children='Page 1' /> more text", dangerously_allow_html=True)

For comparison, here is a regular Markdown link syntax:

dcc.Markdown( "This is text [Page 1](/page1/news) more text")

See multi_page_query_strings/ for an example. For more examples including how to format the link title with Markdown syntax or use an image get the gist.



3. Avoiding duplicate ids

All ids in the entire app must be unique, otherwise callbacks may not fire. Here are some tips to ensure that all ids in the app are unique:

3a. From this forum post as recommended by @chriddyp:

What I’ve done in big projects is to create an id function that creates the prefix automatically. This is easier with pages as each component tree is in its own layout so you can use __name__ as the prefix.

So you’d write something like:

utils.py

def id(name, localid):
    return f"{name.replace('_', '-').replace('.py', '').replace('/', '')}-{localid}"

pages/historical_analysis.py

from utils import id

layout = html.Div(id=id(__name__, 'parent-div'))

3b. From this video by arjancodes

Define ids in module. It makes them easier to access, maintain, and reduces typos. See this used in multi_page_long_callback

for example:

ids.py

PAGE1_BUTTON = "page1-button"
PAGE1_GRAPH = "page1-graph"

page1.py

import ids

html.Button("button", id=ids.PAGE1_BUTTON)
dcc.Graph(ids.PAGE1.GRAPH)

@callback(
    Output(ids.PAGE1_GRAPH, "figure"),
    Input(ids.PAGE1_BUTTON, "n_clicks")
)



4. Display loading screen when page_container is loading

Shows how to make the overall loading screen only display when there is a change to the _pages_content that involves a layout being changed and not changes within the layout. See the post on the Dash Community Forum. Thanks @BSd3v for this example!

5. Preventing Query String Errors

TypeError: layout() got an unexpected keyword argument....

Dash Pages captures query strings and path variables from the URL, passing them to the layout function as keyword arguments. It's recommended to include **kwargs to handle unexpected query strings. This prevents errors if a user enters a query string in the URL, even if you don't plan to use them.

def layout (**kwargs):
    return html.Div("my layout")