Further Elaboration on Multi-Page Apps

Hi,

So I would to ask for some assistance troubleshooting an issue I am running into when creating a dynamic single page app that can serve various pages within it. I did study over the examples provided in these areas to get more clarification:

  1. Multi page app documentation
  2. A few questions on Multi-Page App

While my implementation tried to emulate “Option 3” from resource 1 - I do not believe I may have done so correctly. Here is a copy of basic framework I am trying to build. Also, would you have any suggestions when I start to look at implementing a few heavier calculation based pages and avoid making the app sluggish?

A final observation I made was that when I tested the code without using a template, it could successfully travel between the pages but just didn’t look too stellar.

First Implementation (No template but works):

import panel as pn

pn.extension()

class Page1:
    def __init__(self):
        self.content = pn.Column("# Page 1", "This is the content of page 1.")

    def view(self):
        return self.content

class Page2:
    def __init__(self):
        self.content = pn.Column("# Page 2", "This is the content of page 2.")

    def view(self):
        return self.content

pages = {
    "Page 1": Page1(),
    "Page 2": Page2(),
}

def show_page(page_instance):
    main_area.clear()
    main_area.append(page_instance.view())

page1_button = pn.widgets.Button(name="Page 1", button_type="primary")
page2_button = pn.widgets.Button(name="Page 2", button_type="primary")

page1_button.on_click(lambda event: show_page(pages["Page 1"]))
page2_button.on_click(lambda event: show_page(pages["Page 2"]))

# Create an Accordion for the collapsible sidebar menu
sidebar_menu = pn.Accordion(
    ("Pages", pn.Column(page1_button, page2_button))
)
sidebar_menu.active = []  # None means all sections are collapsed by default

sidebar = pn.Column(sidebar_menu)

main_area = pn.Column(pages["Page 1"].view())

app = pn.Row(sidebar, main_area)

app.servable()

Second Implementation (Using material template but does not work):

import panel as pn
from panel.template import MaterialTemplate

pn.extension()

# Define pages as classes
class Page1:
    def __init__(self):
        self.content = pn.Column("# Page 1", "This is the content of page 1.")

    def view(self):
        return self.content


class Page2:
    def __init__(self):
        self.content = pn.Column("# Page 2", "This is the content of page 2.")

    def view(self):
        return self.content

# Instantiate pages and add them to the pages dictionary
pages = {
    "Page 1": Page1(),
    "Page 2": Page2(),
}

# Function to show the selected page
def show_page(page_instance):
    main_area.clear()
    main_area.append(page_instance.view())

# Define buttons to navigate between pages
page1_button = pn.widgets.Button(name="Page 1", button_type="primary")
page2_button = pn.widgets.Button(name="Page 2", button_type="primary")

# Set up button click callbacks
page1_button.on_click(lambda event: show_page(pages["Page 1"]))
page2_button.on_click(lambda event: show_page(pages["Page 2"]))

# Create the sidebar
sidebar = pn.Column(page1_button, page2_button)

# Create the main area and display the first page
main_area = pn.Column(pages["Page 1"].view())

# Create the Material Template and set the sidebar and main area
template = MaterialTemplate(
    title="Multi-Page App",
    sidebar=sidebar,
    main=main_area,
)

# Serve the Panel app
template.servable()
1 Like

Hi @russkev

Welcome to the community :+1:

You’ve done a single mistake. The sidebar and main arguments should be lists.

The below works

import panel as pn
from panel.template import FastListTemplate

pn.extension()

# Define pages as classes
class Page1:
    def __init__(self):
        self.content = pn.Column("# Page 1", "This is the content of page 1.")

    def view(self):
        return self.content


class Page2:
    def __init__(self):
        self.content = pn.Column("# Page 2", "This is the content of page 2.")

    def view(self):
        return self.content

# Instantiate pages and add them to the pages dictionary
pages = {
    "Page 1": Page1(),
    "Page 2": Page2(),
}

# Function to show the selected page
def show_page(page_instance):
    main_area.clear()
    main_area.append(page_instance.view())

# Define buttons to navigate between pages
page1_button = pn.widgets.Button(name="Page 1", button_type="primary")
page2_button = pn.widgets.Button(name="Page 2", button_type="primary")

# Set up button click callbacks
page1_button.on_click(lambda event: show_page(pages["Page 1"]))
page2_button.on_click(lambda event: show_page(pages["Page 2"]))

# Create the sidebar
sidebar = pn.Column(page1_button, page2_button)

# Create the main area and display the first page
main_area = pn.Column(pages["Page 1"].view())

# Create the Material Template and set the sidebar and main area
template = FastListTemplate(
    title="Multi-Page App",
    sidebar=[sidebar],
    main=[main_area],
)

# Serve the Panel app
template.servable()
panel serve script.py

multipage

Please note that you can also just serve to independent scripts. That will also give you a multipage app.

panel serve script1.py script2.py
2 Likes

That is a great solution.

I am left with one question:

I tried to automate the page generation in order to not forget to set a button:

pages = {
    "Page 1": Page1(),
    "Page 2": Page2()
}

page_buttons = {}
for page in pages:
    page_buttons[page] = pn.widgets.Button(name=page, button_type="primary")
    page_buttons[page].on_click(lambda event: show_page(pages[page]))

This does not lead to an error, however, both buttons point to page 2.

Here is the complete code:

import panel as pn
from panel.template import FastListTemplate

from pages.page_1 import Page1
from pages.page_2 import Page2

pn.extension()


def show_page(page_instance):
    main_area.clear()
    main_area.append(page_instance.view())


pages = {
    "Page 1": Page1(),
    "Page 2": Page2()
}

page_buttons = {}
for page in pages:
    page_buttons[page] = pn.widgets.Button(name=page, button_type="primary")
    page_buttons[page].on_click(lambda event: show_page(pages[page]))

# page1_button, page2_button = page_buttons.values()

# # Set up button click callbacks
# page1_button.on_click(lambda event: show_page(pages["Page 1"]))
# page2_button.on_click(lambda event: show_page(pages["Page 2"]))

sidebar = pn.Column(*page_buttons.values())

main_area = pn.Column(pages["Page 1"].view())

template = FastListTemplate(
    title="Multi-Page App",
    sidebar=[sidebar],
    main=[main_area],
)

template.servable()

The modules page_1 and page_2 only include the class definition as of above:

import panel

class Page1:
    def __init__(self):
        self.content = pn.Column("# Page 1", "This is the content of page 1.")

    def view(self):
        return self.content

(for page 2, respectively)

Thank you very much for your help.