Hi @micdonato
Welcome to the community. The answer to your question depends on your use case and requirements.
Option 1 (preferred): panel serve
multiple files
The simplest is to serve a multi page app using multiple files.
panel serve page1.py page2.ipynb page3.py ...
Add the --autoreload
flag while developing. This will also give you the index page out of the box.
For example for panel-chemistry I just panel serve examples/reference/*.ipynb
( See Link). You can explore the multi-page application on binder here.
Option 2: pn.serve
multiple functions or panels
For some use case you would need to use pn.serve
instead and serve the application using python app.py
. For example if you want to use other urls than the file names.
Run the script via python app.py
app.py
import panel as pn
def page1():
return "page 1"
def page2():
return pn.Column(
"# Page 2", "Welcome to the second page"
)
ROUTES = {
"1": page1, "2": page2
}
pn.serve(ROUTES, port=5006)
Please note that if you don’t pn.serve
functions but instances, then they will be shared across users/ sessions. The latter can be confusing. But it also makes it really simple interact in real time with the same app instance for example for demo/ testing purposes. Another use case for instances is a live updating dashboard that should look the same to all users.
Option 3 Use a Widget to navigate between pages in a Single Page Application.
panel serve
or pn.serve
one page and use a widget and/ or url arguments to navigate between “pages”.
import panel as pn
pn.extension(sizing_mode="stretch_width")
pages = {
"Page 1": pn.Column("# Page 1", "...bla bla bla"),
"Page 2": pn.Column("# Page 2", "...more bla"),
}
def show(page):
return pages[page]
starting_page = pn.state.session_args.get("page", [b"Page 1"])[0].decode()
page = pn.widgets.RadioButtonGroup(
value=starting_page,
options=list(pages.keys()),
name="Page",
sizing_mode="fixed",
button_type="success",
)
ishow = pn.bind(show, page=page)
pn.state.location.sync(page, {"value": "page"})
ACCENT_COLOR = "#0072B5"
DEFAULT_PARAMS = {
"site": "Panel Multi Page App",
"accent_base_color": ACCENT_COLOR,
"header_background": ACCENT_COLOR,
}
pn.template.FastListTemplate(
title="As Single Page App",
sidebar=[page],
main=[ishow],
**DEFAULT_PARAMS,
).servable()
Please note the downside of this approach is that when Panel/ Bokeh replaces one page with another it can be slow if your layout is complex. This should hopefully be fixed when bokeh 3.0 is released.
If you want to have multiple pages in one “single page app” consider using Tabs
with argument dynamic=True
or a custom template. That will make you app more snappy.
Share State Across Pages
If you need to share state across your pages you can use pn.state.cache
for that. It’s just normal python dictionary. You can share Parameterized Classes, DataFrames, Widgets etc if you need to.
Lets take an example show casing some of the possibilities
You can run the below via python name_of_script.py
. Alternatively you can refactor out into seperate files and panel serve
them.
import panel as pn
import param
import datetime
from threading import Thread
import time
pn.extension(sizing_mode="stretch_width")
class StreamClass(param.Parameterized):
value = param.Integer()
class MessageQueue(param.Parameterized):
value = param.List()
def append(self, asof, user, message):
if message:
self.value = [*self.value, (asof, user, message)]
ACCENT_COLOR = "#0072B5"
DEFAULT_PARAMS = {
"site": "Panel Multi Page App",
"accent_base_color": ACCENT_COLOR,
"header_background": ACCENT_COLOR,
}
def fastlisttemplate(title, *objects):
"""Returns a Panel-AI version of the FastListTemplate
Returns:
[FastListTemplate]: A FastListTemplate
"""
return pn.template.FastListTemplate(**DEFAULT_PARAMS, title=title, main=[pn.Column(*objects)])
def get_shared_state():
if not "stream" in pn.state.cache:
state=pn.state.cache["stream"]=StreamClass()
pn.state.cache["messages"]=MessageQueue()
def update_state():
while True:
if state.value==100:
state.value=0
else:
state.value+=1
time.sleep(1)
Thread(target=update_state).start()
return pn.state.cache["stream"], pn.state.cache["messages"]
def show_messages(messages):
result = ""
for message in messages:
result = f"- {message[0]} | {message[1]}: {message[2]}\n" + result
if not result:
result = "No Messages yet!"
return result
def page1():
_, messages = get_shared_state()
user_input = pn.widgets.TextInput(value="Guest", name="User")
message_input = pn.widgets.TextInput(value="Hello", name="Message")
add_message_button = pn.widgets.Button(name="Add")
def add_message(event):
messages.append(datetime.datetime.utcnow(), user_input.value, message_input.value)
add_message_button.on_click(add_message)
return fastlisttemplate("Add Message", user_input, message_input, add_message_button)
def page2():
_, messages = get_shared_state()
ishow_messages = pn.bind(show_messages, messages=messages.param.value)
return fastlisttemplate("Show Messages",pn.panel(ishow_messages, height=600),)
def page3():
stream, _ = get_shared_state()
return fastlisttemplate("Show Streaming Value",stream.param.value,)
ROUTES = {
"1": page1, "2": page2, "3": page3
}
pn.serve(ROUTES, port=5006, autoreload=True)