If a page needs a static asset like an image in the layout, how would you recommend doing this when the asset is in appās assets folder?
i.e. replacing the following:
html.Img(id=āpage_iconā, src=app.get_asset_url(āpage_icon.pngā))
If a page needs a static asset like an image in the layout, how would you recommend doing this when the asset is in appās assets folder?
i.e. replacing the following:
html.Img(id=āpage_iconā, src=app.get_asset_url(āpage_icon.pngā))
Hi @jgaewsky and welcome to the Dash community
The assets folder works with pages/
. You can see an example of images added to an app in dash-labs
here: dash-labs/10-MultiPageDashApp-MetaTags.md at main Ā· plotly/dash-labs Ā· GitHub
See the demo app here: dash-labs/docs/demos/multi_page_meta_tags at main Ā· plotly/dash-labs Ā· GitHub
Thanks, @chriddyp and @AnnMarieW! The pages feature is an excellent addition and makes Dash even better!
I was wondering if it is possible to set up an app structure that allows multiple sub pages per page. The idea is to navigate the pages using a navigation bar at the top, and to add a second navigation bar whenever a page has sub pages. I came up with the following app structure and would like to ask whether you think this is a reasonable approach. For each of the pages, I have a folder containing page-specific stuff: layout, callbacks etc. In my example, page 2 has sub pages:
-- pages
|-- home
|-- home.py
|-- page_1
|-- page_1.py
|-- page_2
|-- sub_item_1
|-- sub_item_1.py
|-- sub_item_2
|-- sub_item_2.py
|-- submenu.py
|-- page_2.py
|-- other pages...
|-- app.py
In principle, I got this to work with the following implementation, but I am not sure if this is the best approach and would appreciate any kind of feedback. I only add navigation links to the navigation bar at the top when āsubā is not a sub string of the āmoduleā value of a page.
app.py:
import dash
import dash_labs as dl
import dash_bootstrap_components as dbc
from dash import html, Input, Output, State
app = dash.Dash( __name__, plugins=[dl.plugins.pages], external_stylesheets=[dbc.themes.BOOTSTRAP])
navbar = dbc.Navbar(
dbc.Container([
html.A(dbc.Row(
dbc.Col(dbc.NavbarBrand("MyApp", className="ms-2")),
align="center",
className="g-0",
),
href="/",
style={"textDecoration": "none"},
),
dbc.Row([
dbc.NavbarToggler(id="navbar-toggler"),
dbc.Collapse(
dbc.Nav([
dbc.NavItem(dbc.NavLink(page['name'], href=page['path'], active="exact")) for page in dash.page_registry.values() if page["module"] != "pages.not_found_404" and "sub" not in page["module"]
],
className="w-100",
),
id="navbar-collapse",
is_open=False,
navbar=True,
),
],
className="flex-grow-1",
),
],
fluid=True,
),
dark=True,
color="dark",
fixed="top",
sticky=True
)
app.layout = html.Div([navbar, dl.plugins.page_container])
@app.callback(
Output("navbar-collapse", "is_open"),
[Input("navbar-toggler", "n_clicks")],
[State("navbar-collapse", "is_open")],
)
def toggle_navbar_collapse(n, is_open):
if n:
return not is_open
return is_open
if __name__ == "__main__":
app.run_server(debug=True)
home.py:
import dash
from dash import html
import dash_bootstrap_components as dbc
dash.register_page(__name__, path="/", order=1)
content = [html.H2("Home")]
layout = html.Div([
dbc.Row(
dbc.Col(content, width={"size": 8, "offset": 1}),
align="center"
)
])
page_2.py:
import dash
from dash import dcc, html
from .submenu import sidebar
dash.register_page(__name__, path="/page_2", order=2)
content = html.Div([html.H2("Page 2")])
layout = html.Div([sidebar, content])
Creating the submenu automatically from the page registry does not work, so I am creating it manually. I assume, page 2 is not fully registered here, but creating it in app.py and importing it from there is not possible because it would be a circular import.
submenu.py:
import dash_bootstrap_components as dbc
from dash import html
sidebar = html.Div([
dbc.Nav([ # this works
dbc.NavLink("Sub Item 1", href="/page_2/item_1", active="partial"),
dbc.NavLink("Sub Item 2", href="/page_2/item_2", active="partial")
], vertical=True, pills=True),
# dbc.Nav([ # this does not work
# dbc.NavLink(page['name'], href=page['path'], active="exact") for page in dash.page_registry.values() if page["module"] != "pages.not_found_404" and "page_2.sub" in page["module"]
# ], vertical=True, pills=True)
], style={"position": "fixed", "padding": "1rem 1rem", "background-color": "#f8f9fa", "height": "100vh"})
sub_item_1.py:
import dash
from dash import html
from ..submenu import sidebar
dash.register_page(__name__, path="/page_2/item_1", order=1)
content = html.Div([html.H2("Page 2 - Item 1")])
layout = html.Div([sidebar, content])
sub_item_2.py:
import dash
from dash import html
from ..submenu import sidebar
dash.register_page(__name__, path="/page_2/item_2", order=1)
content = html.Div([html.H2("Page 2 - Item 2")])
layout = html.Div([sidebar, content])
With this setup, the āactiveā style of the dbc.NavLinks in the navigation bar at the top does not work any more. Any ideas on how to use this feature? Any feedback would be highly appreciated!
Hey @mawe Iām glad you like pages/
Yes, itās possible to have nested folders within the pages
folder. You can find an example here: dash-labs/09-MultiPageDashApp-NestedFolders.md at main Ā· plotly/dash-labs Ā· GitHub
I think your approach for adding the navigation to the top bar is good, and you found a nice workaround for having a conditional side-bar per page. Itās true that in this case itās too early to use the conveinence of looping through the dash.page_registry
because it hasnāt finished building yet. Adding the links directly is a good solution.
Setting active="partial"
instead of active="exact"
in the header NavLink
should make the active style work. However, you probably need to have a different path for the Home page because the current home page path of "/"
will show as active if you set it to active="partial"
This piece of the pages/ design concerns me a bit. I wonder how we could get around this.
One idea: what if dash layouts could accept a function and that function would be called later upon serialization and whatever would be returned would be serialized. So you could have like:
header = lambda: [dcc.Link(p) for p in dash.page_registry]
Hey @mawe
@chriddyp had a good idea. I changed the layouts and the sidebar to functions and it worked
Could you give this a try?
This is the submenu.py. The sidebar is now a function:
import dash_bootstrap_components as dbc
import dash
def make_sidebar():
return dbc.Nav([
dbc.NavLink(page['name'], href=page['path'], active="exact") for page in dash.page_registry.values() if "page_2.sub" in page["module"]
], vertical=True, pills=True)
This is page2.py
(sub_item_1.py
and sub_item_2.py
are similar. Import the make_sidebar
function, and add it to the layout. Note that the layout is also now a function)
import dash
from dash import html
from .submenu import make_sidebar
dash.register_page(__name__, path="/page_2", order=2)
content = html.Div([html.H2("Page 2")])
def layout():
return html.Div([make_sidebar(), content])
Hi @AnnMarieW!
Your solution for the sidebar navigation works beautifully!
Also, I got the active style in the main navigation bar at the top to work by simply checking the current page in app.py and setting the active style accordingly:
dbc.NavItem(dbc.NavLink(page['name'], href=page['path'], active="exact" if page["path"] == "/" else "partial")) for page in dash.page_registry.values() if page["module"] != "pages.not_found_404" and "sub" not in page["module"]
@mawe - Super, glad it worked for you
This will be a good example to include in the dash-labs docs too.
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 multipleapp
declarations. Some members of the community have used this pattern to create multi-page apps instead of the officialdcc.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:
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.
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.
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.
New in dash-labs>=1.0.0:
pages
plug-inpages/
folderlayout
a function - like when creating a custom menu by page, or using query strings.Examples and demos are located in the docs/demos directory.
multi_page_basics
multi_page_example1
dash-bootstrap-components
and some simple callbacks.multi_page_layout_functions
multi_page_long_callback
@app.long_callback()
with pages/
multi_page_meta_tags
multi_page_nested_folders
multi_page_query_strings
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.
@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!
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.
Now you are asking for the hard stuff!
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
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:
register_page
meta information. ORIā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ā¦).
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.