Search code examples
pythonpy-shiny

How to insert a ui.page_navbar() layout using ui.insert()?


I have the following Shiny for Python app that I want to execute where the app starts with a simple welcome page that asks for a value, with a button that the user clicks on when complete. After the button is clicked, a new page renders with a ui.page_navbar layout (including the text value entered at the welcome page).

Here is the code:

from shiny import App, ui, reactive, render

# main ui object for the app
app_ui = ui.page_fluid(
    ui.div(
        (   "Welcome Screen",
            ui.input_text(id='str_val', label='Type in something'),
            ui.input_action_button(id="btn_welcome", label="Proceed!"),
        ),
        id="welcome_screen",
    ),
    ui.div(id="main_page")
)

# main server function for the app
def server(input, output, session):
    @reactive.effect
    @reactive.event(input.btn_welcome, ignore_init=True, ignore_none=True)
    def _():
        ui.remove_ui(selector="div:has(> #welcome_screen)")
        
        ui.insert_ui(
            selector=f"#main-page", 
            where="beforeBegin",
            ui= ui.page_navbar(
                
                ui.nav_panel("Tab 1", "Tab 1 content",ui.output_text('str_val')),
                ui.nav_panel("Tab 2", "Tab 2 content"),
                ui.nav_panel("Tab 3", "Tab 3 content"),
                title="Main Page",
                id="page",
            ),
        )
        
    @render.text
    def str_val():
        return input.str_val()

app = App(app_ui, server)

When I run this, the welcome screen loads and works as expected. When I click on the button, the welcome screen disappears, but then the screen is blank, the ui.page_navbar layout doesn't appear. I have successfully used ui.insert_ui to place content within a div before successfully, but for some reason it isn't working with a page_navbar type tag. I thought maybe the issue was that a ui.page_navbar object couldn't be placed inside a div (where I'm trying to insert it using ui.insert_ui), but in a separate test I was able to do that by just placing it in the app_ui code manually.


Solution

  • The one point is that the selector within the ui.insert_ui contains a typo, you can change it to main_page instead of main-page such that it matches what is defined in the ui.

    The other thing is that ui.remove_ui(selector="div:has(> #welcome_screen)") also removes the main_page div (see the MDN docs on :has()) and then ui.insert_ui with selector=f"#main_page" can't find where to insert the ui. Instead, you can delete the welcome screen by just using ui.remove_ui("#welcome_screen").

    Putting it together, a complete minimal example could be written like this:

    from shiny import App, ui, reactive, render
    
    # main ui object for the app
    app_ui = ui.page_fluid(
        ui.div(
            (   "Welcome Screen",
                ui.input_text(id='str_val', label='Type in something'),
                ui.input_action_button(id="btn_welcome", label="Proceed!"),
            ),
            id="welcome_screen",
        ),
        ui.div(id="main_page")
    )
    
    # main server function for the app
    def server(input, output, session):
        @reactive.effect
        @reactive.event(input.btn_welcome, ignore_init=True, ignore_none=True)
        def _():
            ui.remove_ui("#welcome_screen")
            
            ui.insert_ui(
                selector=f"#main_page", 
                where="beforeBegin",
                ui= ui.page_navbar(
                    
                    ui.nav_panel("Tab 1", "Tab 1 content",ui.output_text('str_val')),
                    ui.nav_panel("Tab 2", "Tab 2 content"),
                    ui.nav_panel("Tab 3", "Tab 3 content"),
                    title="Main Page",
                    id="page",
                ),
            )
            
        @render.text
        def str_val():
            return input.str_val()
    
    app = App(app_ui, server)