@snowde You should be able to open specific tabs⦠Have you tried it with the plug-in it and it didāt work? can you give an example of how you are doing this currently?
Hereās a new feature request:
This plug-in doesnāt yet support pages in nested folders in pages/
. For example, if you have this structure in pages:
- pages
|-- home.py
-chapter1
|-- page1.py
|-- page2.py
-chapter2
|-- page1.py
|-- page2.py
Iām not sure about the best way to handle this, but it might be convenient if dash.page_registry
would also have a folder
variable that would be populated automatically. For the example above āpages.chapter1.app1ā would also have folder=āchapter1ā.
(Note ā in this potential solution, the dash.page_registry
dict is flat, with a key for each app in pages/
)
Then if you were populating submenus for the app, you could create them like this:
html.Div(
[
html.Div(dcc.Link(page["name"], href=page["path"]))
for page in dash.page_registry
if page["folder"] == "chapter1"
]
)
Does anyone have any thoughts or see a better way to do this?
Looks great! It looks similar to this project which creates a template of a multipage dash app https://github.com/ned2/slapdash
That project hasnāt been updated for around 2 years, so doesnāt contain the new features of dash 2. Would be really cool to have this new multipage code in a batteries included template. Is there something like this for dash 2 yet?
This is really exciting and definitely reduces the amount of steps needed to build a multi-page app. I took @Emil 's cool Burger component and built a very simple example app.
One area I wonder whether we can enhance is sharing data between pages. Now Iām using dcc.Store and callbacks, but perhaps there are quicker/simpler ways. @AnnMarieW and I were talking and she was thinking maybe we could do it through the dash.register.pages
ā make it so a dcc.Store is generated automatically through variable name or id.
This app has two pages and the main app.py. See structure below:
home.py (Meat)
import dash
from dash import html, dcc, callback, Output, Input
import pandas as pd
import plotly.express as px
df = pd.read_csv(
"https://gist.githubusercontent.com/chriddyp/c78bf172206ce24f77d6363a2d754b59/raw/c353e8ef842413cae56ae3920b8fd78468aa4cb2/usa-agricultural-exports-2011.csv"
)
print(df.head())
dash.register_page(
__name__,
path="/",
name="Meat",
description="Welcome to my app",
order=0,
icon="fa fa-ambulance",
)
layout = html.Main(
children=[
html.H1("US Meat Exports (2011)"),
html.Div(
dcc.Dropdown(
id="state-slctd",
multi=False,
value=df.state[4],
options=[{"label": x, "value": x} for x in df.state],
style={"width": "60%", "color": "#000000"},
persistence_type="session",
persistence=True,
),
style={"display": "flex", "justifyContent": "center"},
),
html.Div(
id="graph-placeholder",
style={"display": "flex", "justifyContent": "center"},
),
],
style={"width": "100%", "height": "100vh"},
)
@callback(
Output("graph-placeholder", "children"),
Output("stored-state", "data"),
Input("state-slctd", "value"),
)
def update_graph(selected):
dff = df[df.state == selected]
fig = px.bar(dff, x=["beef", "pork", "poultry"])
return dcc.Graph(figure=fig, style={"width": "60%"}), selected
agri.py (Agriculture)
import dash
from dash import html, dcc, callback, Input, Output
import pandas as pd
import plotly.express as px
df = pd.read_csv(
"https://gist.githubusercontent.com/chriddyp/c78bf172206ce24f77d6363a2d754b59/raw/c353e8ef842413cae56ae3920b8fd78468aa4cb2/usa-agricultural-exports-2011.csv"
)
dash.register_page(
__name__, name="Agriculture", path="/agriculture", icon="fa fa-fw fa-heart-o"
)
def layout():
return html.Main(
[
html.H1("US Agricultural Exports (2011)"),
html.Div(
id="graph-placeholder2",
style={"display": "flex", "justifyContent": "center"},
),
],
style={"width": "100%", "height": "100vh"},
)
@callback(
Output("graph-placeholder2", "children"),
Input("stored-state", "data"),
)
def update_graph2(selected):
print(selected)
dff = df[df.state == selected]
fig = px.bar(dff, x=["corn", "wheat", "cotton"], title=f"{selected}")
return dcc.Graph(figure=fig, style={"width": "60%"})
And the main app.py
from dash import Dash, html, dcc
import dash
import pages_plugin
from dash_extensions import Burger
# Example CSS from the original demo.
external_css = [
"https://negomi.github.io/react-burger-menu/example.css",
"https://negomi.github.io/react-burger-menu/normalize.css",
"https://negomi.github.io/react-burger-menu/fonts/font-awesome-4.2.0/css/font-awesome.min.css",
]
app = Dash(__name__, plugins=[pages_plugin], external_stylesheets=external_css)
# print(list(dash.page_registry.values())[1]['icon'])
app.layout = html.Div(
[
Burger(
children=[
html.Nav(
children=[
dcc.Link(
[html.I(className=page["icon"]), html.Span(page["name"])],
href=page["path"],
className="bm-item",
style={"display": "block"},
)
for page in dash.page_registry.values()
],
className="bm-item-list",
style={"height": "100%"},
)
]
),
pages_plugin.page_container,
dcc.Store(id="stored-state"),
],
style={"height": "100%"},
)
if __name__ == "__main__":
app.run_server(debug=True, port=8002)
Hey @adamschroeder
Thanks for posting another nice example. A few cool things here:
- This shows how to have names for the pages that are different than your file names, and how to include other page specific data such as icons:
For example agri.py
sets the name
āAgricultureā and adds the icon
here:
dash.register_page(
__name__, name="Agriculture", path="/agriculture", icon="fa fa-fw fa-heart-o"
)
and in app.py
itās used to set the label on the link:
dcc.Link( [html.I(className=page["icon"]), html.Span(page["name"])], ....
-
Nice use of @Emil 's burger menu!
-
For
dash-bootstrap-component
users, you can also use the newdbc.Offcanvas
component ā new in V1.0. See more info here and itās now easier to add icons
2 posts were split to a new topic: Attempting to connect a callback Input item but no component with that ID exists
Great new feature! Am working with this, but am coming across a weird error. Iām trying to build a landing page in a seperate file, but its not picking up the full list of pages that are registered.
I assume this is something to do with the order in which register_page is called - so when layout() is run in my file the page_registry doesnāt have all of the pages registered yet?
Do you have to have the landing page defined in app.py? When its in app.py it seems to work perfectlyā¦
Or is it something to do with the order=0 parameter in register_page?
Regards,
Amien
Hi @pandamodium
Itās possible to build the landing page in a separate file. Can you say more about how you have things set up?
You can also find some examples here
Hey Ann!
Thanks for replying! Its a basic app with a few pages in a āpagesā folder.
I put the below code in pages/home.py - but the links are only for the first few pages. It looks like it runs in alphabetical order, but only up to the page in question.
app.layout = html.Div(
[
html.H1("App Frame"),
html.Div(
dcc.Link("Go back home", href=dash.page_registry["pages.home"]["path"])
),
html.Div(
[
html.Div(
dcc.Link(f"{page['name']} - {page['path']}", href=page["path"])
)
for page in dash.page_registry.values()
if page["module"] != "pages.not_found_404"
]
),
dl.plugins.page_container,
]
)
Hi @pandamodium
There is still not enough info in your post for me to help. The pages are all registered when the app starts, so timing shouldnāt be an issue.
Here are a couple debugging tips:
You can use your IDEās debugger tool to inspect dash.page_registry
or you can do it the āhard wayā and print it.
if you simply use: print(dash.page_registry)
, you may get something thatās pretty hard to read. To print it in a nicer format, , I like to remove the layout
key in the nested dict and format it with json.dumps
Hereās an example:
Note - Dash pages
is now available in dash-labs v1.0.0 as a plug-in. (pip install dash-labs
)
import dash_labs as dl
app = Dash(__name__, plugins=[dl.plugins.pages])
# --- debugging - print `dash.page_registry`
import json
registry = {a:{c:d for c, d in b.items() if c != 'layout'} for a, b in dash.page_registry.items()}
print(json.dumps(registry, indent=4))
# ------------------------------------
...
Is the page you are looking for not in dash.page_registry
? How did you define the dash.register_page
in home.py
? Adding path="/"
will make it the landing page:
dash.register_page(__name__, path="/")
Thanks AnnMarieW!
I put that code in - and was still getting the same effect, but I think it worked it out!
The solution was to make sure the layout definition was a function. so my home.py becomes:
def layout():
return dbc.Container([
html.Div(
dcc.Link('Go back home', href=dash.page_registry['pages.home']['path'])
),
html.Div([
html.Div(dcc.Link(
f"{page['name']} - {page['path']}",
href=page['path']
))
for page in dash.page_registry.values()
if page['module'] != 'pages.not_found_404'
]),
])
It seems that if you have the home page in a separate page - it will generate the layout objects in the python files in the āpagesā folder when you start (in alphabetical order), and so if your file is called home.py, nothing after H will be in the page_registry at the time that code is run.
Anyway if anyone else has this issue - just remember to use a function for the layout object.
Regards,
Amien
Itās not supposed to work that way, so this might be a bug. You should be able to define any of the pages to be the home page without having to make the layout a function.
The code I provided was to help with debugging and not to fix it. It would be very helpful if you shared what was printed and how you had dash.register_page(...)
defined in each of the apps in the pages/
Hey Ann!
Sure thing - see below for a stripped down version showing the behaviour in practice:
pages/home.py:
import dash
from dash import Dash, html, dcc,callback, Input, Output, State
import dash_bootstrap_components as dbc
# --- debugging - print `dash.page_registry`
import json
registry = {a:{c:d for c, d in b.items() if c != 'layout'} for a, b in dash.page_registry.items()}
print(json.dumps(registry, indent=4))
# ------------------------------------
dash.register_page(
__name__,
path='/',
name='Old Landing',
description='Welcome to Athena',
order=0,
redirect_from=['/old-home-page', '/v2'],
extra_template_stuff='yup'
)
layout = dbc.Container([
html.Div(
dcc.Link('Go back home', href=dash.page_registry['pages.home']['path'])
),
html.Div([
html.Div(dcc.Link(
f"{page['name']} - {page['path']}",
href=page['path']
))
for page in dash.page_registry.values()
if page['module'] != 'pages.not_found_404'
]),
])
app.py:
from dash import Dash, html, dcc
import dash
import pages_plugin
app = Dash(__name__, plugins=[pages_plugin])
dash.register_page('another_page', layout='Another page', path='/another-page')
dash.register_page('and_again', layout='And again!', path='/and-again')
app.layout = html.Div([
html.H1('App Frame'),
pages_plugin.page_container
])
if __name__ == '__main__':
app.run_server(debug=True,port=8111)
The result from running python app.py:
Apologies and the logging output was:
Dash is running on http://127.0.0.1:8111/
* Serving Flask app 'app' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
{
"pages.analyticapps": {
"module": "pages.analyticapps",
"supplied_path": "/oldhome",
"path": "/oldhome",
"supplied_name": "Analytic Apps",
"name": "Analytic Apps",
"supplied_title": null,
"title": "Analytic Apps",
"supplied_description": "Welcome to my app",
"description": "Welcome to my app",
"supplied_order": 0,
"supplied_layout": null,
"extra_template_stuff": "yup",
"image": "app.jpeg",
"supplied_image": null,
"redirect_from": null
},
"pages.historical_archive": {
"module": "pages.historical_archive",
"supplied_path": null,
"path": "/historical-archive",
"supplied_name": null,
"name": "Historical archive",
"supplied_title": null,
"title": "Historical archive",
"supplied_description": null,
"description": "Historical archive",
"supplied_order": null,
"supplied_layout": null,
"image": "app.jpeg",
"supplied_image": null,
"redirect_from": null
}
}
{
"pages.analyticapps": {
"module": "pages.analyticapps",
"supplied_path": "/oldhome",
"path": "/oldhome",
"supplied_name": "Analytic Apps",
"name": "Analytic Apps",
"supplied_title": null,
"title": "Analytic Apps",
"supplied_description": "Welcome to my app",
"description": "Welcome to my app",
"supplied_order": 0,
"supplied_layout": null,
"extra_template_stuff": "yup",
"image": "app.jpeg",
"supplied_image": null,
"redirect_from": null
},
"pages.historical_archive": {
"module": "pages.historical_archive",
"supplied_path": null,
"path": "/historical-archive",
"supplied_name": null,
"name": "Historical archive",
"supplied_title": null,
"title": "Historical archive",
"supplied_description": null,
"description": "Historical archive",
"supplied_order": null,
"supplied_layout": null,
"image": "app.jpeg",
"supplied_image": null,
"redirect_from": null
}
}
@pandamodium
Thanks so much for sharing the output, I think see whatās going on now.
The links should be populated in app.py
:
html.Div([
html.Div(dcc.Link(
f"{page['name']} - {page['path']}",
href=page['path']
))
for page in dash.page_registry.values()
if page['module'] != 'pages.not_found_404'
]),
And this bit of debugging code below should go right before the layout in app.py
. If you put it in one of the apps in pages/
, it will only show whatās been added up to that point.
To confirm my theory, could you give it a try? Itās also odd that "pages.analyticapps"
shows up twice in the dict output you provided.
# --- debugging - print `dash.page_registry`
import json
registry = {a:{c:d for c, d in b.items() if c != 'layout'} for a, b in dash.page_registry.items()}
print(json.dumps(registry, indent=4))
# ------------------------------------
Hey Ann!
Yes thatās exactly what happens!
Unless you make the Layout in home.py a function, in which case it updates the layout each time it is called and populates the full list.
And I think analytics app is being repeated because I have debug=True, Iāve noticed it duplicates all my logging stuff if I have that on.
Will paste the logs later this morning for you to review.
Thanks again for all your help!
Regards
Amien
Hi all,
This is so timely! Iām working on a multi-page app that takes into consideration our physical and labor capacity and need to show different views. I was struggling to try to figure out a way to use the tabs and make it work. Iāve gotten a few pages stitched together and noticed one thing.
I have persistence turned on for many of my inputs but I noticed that whenever I return back to that page the data is not storing. I have persistence turned on and it worked previously, but now that Iām using a multi page app the data is not persisting when I switch back to the link. Is there a way to resolve this issue or is it a bug?
dcc.Dropdown(
id='auction_dropdown',
options=[{'label': auction, 'value': auction}
for auction in ['AAAW', 'FAAO', 'SVAA', 'MAA', 'BWAE', 'GCAA', 'PXAA', 'DFWA', 'BCAA', 'GOAA']],
value=['AAAW', 'FAAO', 'SVAA', 'MAA', 'BWAE', 'GCAA', 'PXAA', 'DFWA', 'BCAA', 'GOAA'], # Default value to show
persistence=True,
multi=True,
searchable=False
),
Hi @johnkangw
Welcome to the Dash community and thanks for trying the new pages plug-in
I just tried this and it worked fine for me ā selections persisted when changing pages. I just copied your dropdown into one of the apps in the pages folder in this dash-labs example.
Can you provide a minimal example that replicates the issue?
Ann,
Sure thing!
I have a separate page in (/pages) with a DataTable that takes in a dataframe (ws_retail_forecast) with persistence turned to True.
dash_table.DataTable(
id='table_retail',
columns=[{'id': i, 'name': i, "format": {
"specifier": ".1f"}} for i in (ws_retail_forecast.reset_index()).columns],
data=(ws_retail_forecast.reset_index()).to_dict('records'),
editable=True,
persistence=True
),
The issue is that whenever I navigate away from the page and go back the data does not persist.
Second question:
Iām using the manual āpages_plugin.pyā in when I startup the app
app = Dash(__name__, plugins=[pages_plugin], external_stylesheets=[dbc.themes.BOOTSTRAP, dbc_css])
The issue is that when I try to use the code from the dash_labs
app = Dash(__name__, plugins=[dl.plugins.pages], external_stylesheets=[dbc.themes.BOOTSTRAP, dbc_css])```
I get an error for this line of the app.,py file:
html.Div(
dcc.Link('Go back home', href=dash.page_registry['pages.home']['path'])
),
The error is:
File āC:/Users/Jkang1/Cox Automotive/Recon Industrial Engineering Team - General/Capacity Model/main_python_files/app.pyā, line 29, in
dcc.Link(āGo back homeā, href=dash.page_registry[āpages.homeā][āpathā])
KeyError: āpages.homeā
Maybe dash_labs can't find pages.home? I have the home.py file in the pages folder.
Hi @johnkangw
hmm⦠looks right to me. In your home.py
file do you have:
dash.register_page( __name__, path="/")
One way to help with debugging is to include the code below in app.py
. Place it right before the layout. (it will show whatās included in dash.page_registry
:
import json
registry = {a:{c:d for c, d in b.items() if c != 'layout'} for a, b in dash.page_registry.items()}
print(json.dumps(registry, indent=4))
I havenāt used persistence
with the datatable. Does the dropdown work for you?