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

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="/")
1 Like

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

@pandamodium

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:

image

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:
image

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

2 Likes

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 :slight_smile:

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?

Ann,
Thanks so much for the help. I checked the home.py and it does have that:

import dash
from dash import html


dash.register_page(
	__name__,
	path='/',
	name='Capacity Model homepage',
	description='Welcome to my app',
	order=0,
	redirect_from=['/old-home-page', '/v2'],
	extra_template_stuff='yup'
)

layout = html.Div([
    html.H1('Homepage for the app'),
    html.H2('Notes to be populated soon')
])

The JSON dump looks about right:

{
    "pages.home": {
        "module": "pages.home",
        "supplied_path": "/",
        "path": "/",
        "supplied_name": "Capacity Model homepage",
        "name": "Capacity Model homepage",
        "supplied_title": null,
        "title": "Capacity Model homepage",
        "supplied_description": "Welcome to my app",
        "description": "Welcome to my app",
        "supplied_order": 0,
        "supplied_layout": null,
        "extra_template_stuff": "yup",
        "image": "home.jpeg",
        "supplied_image": null,
        "redirect_from": [
            "/old-home-page",
            "/v2"
        ],
        "order": 0
    },
    "pages.not_found_404": {
        "module": "pages.not_found_404",
        "supplied_path": "/404",
        "path": "/404",
        "supplied_name": null,
        "name": "Not found 404",
        "supplied_title": null,
        "title": "Not found 404",
        "supplied_description": null,
        "description": "Not found 404",
        "supplied_order": null,
        "supplied_layout": null,
        "image": "app.jpeg",
        "supplied_image": null,
        "redirect_from": null
    },
    "pages.production_tracker": {
        "module": "pages.production_tracker",
        "supplied_path": "/production-tracker",
        "path": "/production-tracker",
        "supplied_name": "Production Tracker",
        "name": "Production Tracker",
        "supplied_title": "Production Tracker",
        "title": "Production Tracker",
        "supplied_description": "This is the Production Tracker",
        "description": "This is the Production Tracker",
        "supplied_order": 2,
        "supplied_layout": null,
        "image": "birds.jpeg",
        "supplied_image": "birds.jpeg",
        "redirect_from": null
    },
    "pages.rvp_view": {
        "module": "pages.rvp_view",
        "supplied_path": "/regional",
        "path": "/regional",
        "supplied_name": "Regional View",
        "name": "Regional View",
        "supplied_title": "Regional View",
        "title": "Regional View",
        "supplied_description": "This is the Regional view",
        "description": "This is the Regional view",
        "supplied_order": 1,
        "supplied_layout": null,
        "image": "birds.jpeg",
        "supplied_image": "birds.jpeg",
        "redirect_from": null
    }
}
Dash is running on http://localhost:8010/
 * 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

@AnnMarieW that is a great question about the dropdown! That persistence actually works! It stays in between pages and works.

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
    )

The above dropdown code is on two different pages with the same id. If I update it and navigate to the any page it persists. Maybe the persistence is an issue with the datatable? Thanks so much for your help with persistence and with looking into why the dash_labs doesn’t work (have to use pages_plugin.py).

OK, good to know that the dropdown works. It looks like the DataTable needs a little more investigation.

The dash-labs version should work. Can you try adding an __init__py file to the root of the pages folder and see if that fixes it?

@AnnMarieW Thanks for looking into the DataTable. Is there anything I need to add to the:

__init__.py

file? I just have a blank file in the pages folder. I added that in and re-ran the app but received the same error

app = Dash(__name__, plugins=[dl.plugins.pages], external_stylesheets=[dbc.themes.BOOTSTRAP, dbc_css])

error:

  File "C:/Users/Jkang1/Cox Automotive/Recon Industrial Engineering Team - General/Capacity Model/main_python_files/app.py", line 26, in <module>
    dcc.Link('Go back home', href=dash.page_registry['pages.home']['path'])
KeyError: 'pages.home'```

@AnnMarieW Oh by the way I got the ThemeChangerAIO to work for my pages and graphs! Looks super cool. The only thing that I am not able to update are the dropdown theme (I don’t think they have a theme). Would be super cool if there was a way to do that. I think from looking at the documentation the dropdowns only take in ‘style’ and not a theme. Maybe something to consider adding in as an option for the future (theme)?

Hey @johnkangw Glad you got the ThemeChangerAIO working! You just made my day! :grin:

There is also a ThemeSwitchAIO to toggle between two themes. See this post for more info: Dash Bootstrap Templates V1.0.0 New: Theme Switch Components

I’m working on a stylesheet to style Dash Core Components and the DataTable with a Bootstrap theme. The first version is now available. The initial goal is to make sure the components are usable in both light and dark themes (ie the text is visible) and to use the Bootstrap “primary” color as the accent color.

To give it a try, add this stylesheet to your external_stylesheets


dbc_css = (
    "https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates@V1.0.1/dbc.min.css"
)
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP, dbc_css])

Then in the layout, add the dbc class to either certain components or the entire layout like this:

app.layout = dbc.Container(
    [
        ...
    ],
    className="dbc",
    fluid=True,
)

Currently, the dropdown doesn’t have any accent colors - it’s just styled generically so the text is visible in all themes. If you have suggestions to improve the style, let me know :slight_smile: