0

I am trying to insert tabs dynamically calling the insertTab() function within a module. For some reason my approach does not work. I guess the problem is how I pass the tabsetPanel id and the value of an existing tabPanel (next to which a tab should be added) to the module.

actionButUI = function(id, label=NULL) {
  ns = NS(id)
  tagList(
    actionButton(ns("button"), label = label)  
  )
}

actionBut = function(input, output, session, tabsetPanel_id, target) {
  observeEvent(input$button, {
    insertTab(
      inputId = tabsetPanel_id(), 
      tabPanel(
        "Dynamic", "This a dynamically-added tab"
      ),
      target = target
    )
  })
}




ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      actionButUI("append_tab", "Insert Tab")
    ),
    mainPanel(
      tabsetPanel(id = "tabs",
                  tabPanel("Hello", "This is the hello tab"),
                  tabPanel("Bar", "This is the bar tab")
      )
    )
  )
)
server <- function(input, output, session) {
  callModule(actionBut, "append_tab", reactive({input$tabs}), "Bar")
}

shinyApp(ui, server)
MrNetherlands
  • 920
  • 7
  • 14
  • 1
    There are some syntax issues in your code that I didn't adress in my answer. You should pass `"tabs"` as the `tabsetPanel_id` argument and access it as `tabsetPanel_id` in the module, not `tabsetPanel_id()` – Gregor de Cillia Mar 29 '18 at 19:00

2 Answers2

2

There seems to be an issue with namespaces. The followig modification fixes the issue

tabsetPanel(id = "append_tab-tabs",
            tabPanel("Hello", "This is the hello tab"),
            tabPanel("Bar", "This is the bar tab"))

The insertTab function tries to add a ui element in the module namespace rather than the global one. If you look at the source code of insertTab you'll see the line

inputId <- session$ns(inputId)

which causes this behavior.

Another way is to pass the session variable from the main app to insetTab rather than the module's session.

actionBut = function(input, output, session, tabsetPanel_id = "tabs", target) {
  ## do some environment hacking: Get the `session` variabe from the
  ## environment that invoked `callModule`.
  parentSession <- get("session", envir = parent.frame(2))

  observeEvent(input$button, {
    insertTab(
      inputId = tabsetPanel_id, 
      tabPanel(
        "Dynamic", "This a dynamically-added tab"
      ),
      target = target,
      session = parentSession
    )
  })
}

This approach can get quite messy however if you work with nested modules.

Gregor de Cillia
  • 7,397
  • 1
  • 26
  • 43
1

An alternative to the InsertTab function, you can follow Ramnath solution here.

I have made it into modules.

library(shiny)

#---- Module Add dynamic tab ---
SidebarUi <- function(id) {
  ns <- NS(id)
  uiOutput(ns("sidebar"))
}

MainpanelUi <- function(id) {
  ns <- NS(id)
  uiOutput(ns("mainpanel"))
}
DynamicTabserver <- function(input, output, session) {
  ns <- session$ns
  output$sidebar <- renderUI({
    actionButton(ns("nTabs"), label = "Add tab")
  })
  output$mainpanel <- renderUI({
    uiOutput(ns('mytabs'))
  })

  output$mytabs <- renderUI({
    nTabs = input$nTabs
    myTabs = lapply(paste('Tab', 0:nTabs), tabPanel)
    do.call(tabsetPanel, myTabs)
  })

}

#---- App.R ---
ui = pageWithSidebar(headerPanel('Dynamic Tabs'),
                     sidebarPanel(SidebarUi("tabdemo")),
                     mainPanel(MainpanelUi("tabdemo")))
server = function(input, output, session) {
  callModule(DynamicTabserver, "tabdemo")
}
shinyApp(ui, server)
user5249203
  • 4,436
  • 1
  • 19
  • 45
  • Thanks for providing Ramnath solution wrapped in modules. However, using this approach I struggle to integrate different ui elements in each tab, e.g. different plots – MrNetherlands Mar 30 '18 at 15:37