Modal Stacking Twice With Additional Nav_Panels / Struggling with Deleting Tabs

Hello! I've started using Shiny for Python to design a page in which users can dynamically create new tabs to view different aspects of their data. The general flow of control is as follows:

  • User presses "+" to create a new tab
  • User presses "query" input_task_button for a modal to pop up
  • User inputs their query specifications in the modal and hits an "execute" input task button
  • User has their data returned to them as both a DataGrid and a Plotly Figure

I've stumbled across 2 issues with my design flow however. If the user ends up clicking the "+" tab multiple times, then hits the "query" input_task_button, the modal will stack on top of on another based on the number of times the user pressed "+".

Additionally, I can create new tabs but I'm not sure of the logic to properly remove tabs. My thought was to use a "red X" svg icon within the nav_panel and use javascript to set it as an InputValue. Then use a reactive.effect to delete the tab. That line of thinking only got me part of my solution and deletes the "latest" tab but for instance, if I want to delete "Tab 1" in a series consisting of 3 ui.nav_panels, it deletes "Tab 3" instead. Then when I delete "tab 3" and try to recreate it, it restores the old DataGrid and Plotly Figure.

My code is as follows:

# Imports
import pandas as pd
from datetime import datetime, date
from shiny import App, Inputs, Outputs, Session, module, reactive, render, ui
from shinywidgets import output_widget, render_widget
from faicons import icon_svg

# Module Functions
@module.ui
def nav_panel_ui(panelNumber):
    return ui.nav_panel(
        f"View {panelNumber}",
       ui.card(
            ui.card_header("Directions"),
            ui.p("Start by selecting the Query button. A modal will pop up asking the user to input their query."),
            ui.input_task_button(id = f"query_btn_{panelNumber}",
                                label = "Query!",
                                label_busy = "Please wait...",
                                type = "success"),
           id = f"panel{panelNumber}"
    ),
    ui.output_ui(f"tab_{panelNumber}"),
    value = f"View_{panelNumber}",
    icon = icon_svg(name="xmark", fill="red", fill_opacity = "0.6")
)

@module.server
def nav_panel_server(input, output, session, panelNumber):

    @reactive.effect
    @reactive.event(input[f"query_btn_{panelNumber}"], ignore_init = True)
    def query_modal():
        ui.modal_show(
            ui.modal(
                ui.accordion(
                    ui.accordion_panel("SQL Query",
                    ui.input_text_area(id = f"sql_query_{panelNumber}",
                                                            label = "Type a SQL Query"),
                    ui.input_task_button(id = f"sql_query_btn_{panelNumber}",
                                                                label = "Query",
                                                                label_busy = "Please wait...")
                                                            ),
                    ui.accordion_panel("Historical Query",
                    ui.input_text_area(id = f"hist_query_{panelNumber}",
                                                            label = "Type a Historical Query"),
                    ui.input_task_button(id = f"hist_query_btn_{panelNumber}",
                                                                label = "Query",
                                                                label_busy = "Please wait...")
                                                            ),
                    open = "SQL Query"
                    ),
            size = 'xl',
            footer = None,
            title = "Select how to query"
            )
        )

    @reactive.effect
    @reactive.event(input[f"sql_query_btn_{panelNumber}"], input[f"hist_query_btn_{panelNumber}"])
    def remove_directions():
        ui.modal_remove()
        ui.remove_ui(selector=f"#{int(panelNumber)-1}-panel{panelNumber}")

# App Interfance
app_ui = ui.page_fluid(
    ui.output_ui("tab_UI")
)

# Server Logic
def server(input, output, session):

    navs = reactive.value(0)

    @reactive.effect
    @reactive.event(input.gui_tabs)
    def add_tabs():
        if input.gui_tabs() == "+":
            navs.set(navs.get() + 1)

    @output
    @render.ui
    def tab_UI():
        
        [nav_panel_server(str(x), panelNumber=str(x+1)) for x in range(navs.get())]
        ui.update_navs("gui_tabs", selected = f"View_{navs.get()}")
        
        return ui.navset_tab(
           ui.nav_panel("Home",
           value = "panel0"
             ),
            *[nav_panel_ui(str(x), panelNumber=str(x+1)) for x in range(navs.get())],
            ui.nav_panel("+"),
           id = "gui_tabs"
    )

app = App(app_ui, server)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.