📣 Introducing Dash `/pages` - A Dash 2.x Feature Preview

@mawe - Super, glad it worked for you :confetti_ball:
This will be a good example to include in the dash-labs docs too.

1 Like

Hi Ann, after reading all the posts, I am wondering if you can help me undrstand the difference in creating multiple dash apps and putting them through flask, versus the multi-page approach above. I see a lot of people are pushing their apps through flasj, what benefits and advantages are theit to this approach. This would be very helpfull, because it would require a lot of fundamental changes to go from one to the other. Best Derek.

Hi @snowde

I haven’t made a multi-page app using flask and multiple apps so I can’t speak to the pros and cons. (If someone else has, please chime in).

However, this is from the Dash 2.0 announcement:

@dash.callback will not work if your project has multiple app declarations. Some members of the community have used this pattern to create multi-page apps instead of the official dcc.Location multi-page app solution.. The multi-app pattern was never officially documented or supported by our team.

We built and officially support the dcc.Location method of multiple pages vs multiple flask instances for a couple of reasons:

  • “Single page app (SPA)” links with dcc.Link: This allows page navigation without reloading the browser page (and therefore reloading and re-evaluating the JS scripts and CSS), making page navigation quite a bit faster
  • Ability to share common components in the “frame” of the page rather than redefining within each page like headers and sidebars
  • Ability to share data like dcc.Store
  • More easily use query parameters in dash callbacks
  • “It’s just Dash” - dcc.Location and dcc.Link provide a multi page app experience using the same simple foundational principles of dash: Rich components tied together with callback functions

Now with /pages, we’re adding even more functionality out of the box (see original post) that you would otherwise need to program from scratch using the flask method.

3 Likes

Thanks, Chris that makes this method really appealing, but I must ask if you could also comment on the possible benefits of using the Flask method, for example, I have read that it is useful for user authentication. Happy holidays.

Latest Dash Labs Docs and Examples:

Based on @vuthanhdatt 's question in this post, see the new example of how to pass parameters to pages using query strings.

Based on the question from @bigmike and @vnnw in this post, there is now an example of how to use @app.long_callback with pages/.

The new chapter on layout functions is based on @mawe’s question on how to make a nav menu for certain pages. Special thanks to @pandamodium for pointing out that it’s necessary to make the layout a function if you are using dash.page_registry from within the `pages/ folder.

Note - This documentation is a work in progress. Please feel free to make suggestions for improvements by commenting here or making a pull request in dash-labs.

Multi-Page App Docs

New in dash-labs>=1.0.0:

Multi-Page App Demos

Examples and demos are located in the docs/demos directory.

  • multi_page_basics
    • Minimal examples of all the features and basic quickstart apps. (see chapter 8 for details.)
  • multi_page_example1
    • A quickstart app using dash-bootstrap-components and some simple callbacks.
  • multi_page_layout_functions
    • An app that creates a sidebar menu for certain pages. (See chapter 11 for details.)
  • multi_page_long_callback
    • An example of how to use @app.long_callback() with pages/
  • multi_page_meta_tags
    • The example app used to show how the meta tags are generated. (See chapter 10 for details.)
  • multi_page_nested_folders
    • This is the example app used in chapter 9.
  • multi_page_query_strings
    • An example of using query strings in the URL to pass parameters from one page to another.

Installation

dash-labs is regularly updated, so be sure to install the latest version:

$ pip install -U dash-labs

New feature coming soon: Variables in the pathname.

3 Likes

Awesome release @AnnMarieW and many thanks to everyone involved!

1 Like

@AnnMarieW Is there a way to incorporate flask decorators in with the /pages functionality? For example, there were a few packages that I was looking at in flask that help you restrict access to certain pages based on user auth status using a decorator - would I need to use the previous url router callback to handle that or do you happen to know of a way to incorporate that into here? Thanks! :slight_smile:

I would love to see this include customizing the meta data based on the variables.

Example:
We might have urls like /news/sports/1 and /news/sports/2. Each of these articles cover different topics and the article title and overview should be displayed when sharing instead of a generic text about sports articles.

@raptorbrad

Now you are asking for the hard stuff! :sweat_smile:

That feature came up during the code review for handling variables in the pathname, but we decided to start with the generic version. I wasn’t sure how popular this would be – but since you brought it up before this was even released, I’ll open an issue on Gitub for the feature request.

Thanks for the suggestion - you can track the progress here.

@raptorbrad Update: Here’s a potential solution . Feedback is welcome :slightly_smiling_face:

I supposed that solution does work, but I don’t think it’s ideal for more dynamic page. I’m not fully aware of how the meta tags work, but a solution I thought of is either:

  1. some meta component defined in the layout that overrides the register_page meta information. OR
  2. an additional return object within the layout function that is a dictionary of meta tags

I’m not sure if either of these are feasible, since I’m not too familiar with what’s going on under the hood.

Another follow-up thought to this thread - would be awesome as multipage apps start to become more supported by Dash if there was an extension to it that tackled the namespace issues discussed in this thread - I find that I sometimes copy and paste code between pages / naturally end up using similar naming schemes in different pages, which muddies up the namespace. Would be awesome if this register page feature was able to incorporate a uuid into each page declaration and prepend it to the callbacks and the id’s of that page (I personally never need a callback to be able to access information from separate pages, but I guess the ability to remove the uuid for spot cases would also be nice if needed in a niche case to access something elsewhere…).

1 Like

I agree on this point, @dash-beginner. Maybe we could do something similar to the implementation in dash-extensions where each page has an optional prefix argument which is prefixed onto the IDs of the involved component and callbacks to avoid ID collisions between pages.

3 Likes

Hey all, thanks for the extensive work on this project. The multi-page app solution presented here and the other improvements to the development repo since the Dash 2.0 release are exciting! Since there hasn’t been a release since 2.0 back in August/September, can we expect 2.1 or another minor update to ship with these changes in the near future?

Hey @Emil and @dash-beginner

ID clashes have been an issue for a long time, especially in multi-page apps. A prefix is a good idea.

Emil, does your implementation of adding a prefix work with the current version of Dash? I’ve run into issues doing this in certain cases in Dash>=1.20 and I think it’s because of this change.

1 Like

Hi @jborman-exos - welcome to the Dash community and I’m glad you like `pages/ :slight_smile:

Dash 2.1 is going to be released soon, and since there’s still more work to do with pages/, it may not be ready in time.

However it is available in dash-labs. I encourage you to give it a try – and feedback is appreiciated!

I just tested it on Dash 2.0, and it seems to work just fine. Here is a small app that demonstrates the concept,

from dash_extensions.enrich import DashProxy, html, Output, Input, dcc
from dash_extensions.multipage import app_to_page, PageCollection, CONTENT_ID, URL_ID


def get_page(i: int):
    # Create a small app that represents the desired page.
    app = DashProxy()
    app.layout = html.Div([html.Button("Click me", id="btn"), html.Div(f"I am app {i}", id="log")])

    @app.callback(Output("log", "children"), Input("btn", "n_clicks"))
    def on_click(n_clicks):
        return f"You clicked {n_clicks} times!"

    # Convert the app to a page.
    return app_to_page(app, id=f"app{i}", label=f"App {i}"). # the id argument here is the prefix


# Create a few pages.
pc = PageCollection([get_page(i) for i in range(5)])
links = [html.A(children=page.label, href="/{}".format(page.id)) for page in pc.pages]
# Embed the pages in the main app.
app = DashProxy(suppress_callback_exceptions=True, prevent_initial_callbacks=True)
app.layout = html.Div(links + [html.Br(), html.Div(id=CONTENT_ID), dcc.Location(id=URL_ID)])
# Register callbacks.
pc.navigation(app)
pc.callbacks(app)

if __name__ == '__main__':
    app.run_server(port=8889)

It embeds five pages that all have ID clashes, but the difference in prefixes make it work.

Screenshot 2022-01-11 at 20.32.53

1 Like

Hey @raptorbrad Thanks for your feedback on the first attempt at a solution for customizing the meta tags based on the variables in the pathname. I added your comments to the issue here:

I’m leaving the issue open - and pull requests are welcome if anyone would like to help :slight_smile:

Here is a situation:
There are some page: a login page, a system page, and a main page.
When I click login button in main page, I will go to login page, but I need to check if I logined.
When I have logined, I will go to system page.
The question is : How to redirect to system page from login page automatically.

Hi all,
I’ve got the multi-page app working successfully on pythonanywhere but am trying to deploy to a Dash Enterprise server. The pages don’t work because I believe in pythonanywhere I have a wsgi file I can set the working directory. I have the app.py and the pages folder one level into the repository.

I can get the app working but the multiple pages aren’t working. The log file is giving me an error stating that ‘Paths that aren’t prefixed with requests_pathname_prefix are not supported.’ Any idea how I can fix this? I asked during our onboarding session but the Plotly folks on the call were not as familiar with how the pages.py worked.

2022-01-25T19:11:28.318126649Z app[web.1]:   File "/app/.heroku/python/lib/python3.9/site-packages/dash_labs/plugins/pages.py", line 260, in update
2022-01-25T19:11:28.318132049Z app[web.1]:     path_id = app.strip_relative_path(pathname)
2022-01-25T19:11:28.318136949Z app[web.1]:   File "/app/.heroku/python/lib/python3.9/site-packages/dash/dash.py", line 1562, in strip_relative_path
2022-01-25T19:11:28.318142849Z app[web.1]:     return strip_relative_path(self.config.requests_pathname_prefix, path)
2022-01-25T19:11:28.318148449Z app[web.1]:   File "/app/.heroku/python/lib/python3.9/site-packages/dash/_utils.py", line 85, in strip_relative_path
2022-01-25T19:11:28.318154049Z app[web.1]:     raise exceptions.UnsupportedRelativePath(
2022-01-25T19:11:28.318179349Z app[web.1]: dash.exceptions.UnsupportedRelativePath: Paths that aren't prefixed with requests_pathname_prefix are not supported.
2022-01-25T19:11:28.318186349Z app[web.1]: You supplied: /chapter/shop_hours_backup and requests_pathname_prefix was /capacity/
2022-01-25T19:11:28.318793252Z app[web.1]: 172.17.0.21 - - [25/Jan/2022:19:11:28 +0000] "POST /capacity/_dash-update-component HTTP/1.1" 500 290 "https://dash-cox-automotive.plotly.host/chapter/shop_hours_backup" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36"
1 Like