Search code examples
user-interfaceshinydynamictabs

Dynamic rendering of N shiny tabs


I try to develop an R shiny app that renders n tabs. n should be hard coded in the server function and not be modified by the user. From these n rendered tabs, initially only the first tab should be displayed and all the others should be hidden. Each tab should includes a button that should switch to the next tab until n is reached. Very greatful for any hints

Here is the last test which did not run:

library(shiny)
library(shinyjs)


ui <- fluidPage(
  mainPanel(
    uiOutput("tabs")
  )
)

server <- function(input, output) {
  
  num_tabs <- 5  # Define the number of tabs
  
  output$tabs <- renderUI({
    tabs <- lapply(1:num_tabs, function(i) {
      tabPanel(
        paste("Tab", i),
        actionButton(inputId = paste0("switch_tab_", i), label = "Switch Tab")
      )
    })
    
    tabsetPanel(
      id = "tabs_content",
      do.call(tabPanel, c(tabs, id = paste0("tab_", 1:num_tabs)))
    )
  })
  
  observe({
    for (i in 2:num_tabs) {
      runjs(paste("$('.nav-tabs li:nth-child(", i, ")').hide();"))
      runjs(paste("$('.tab-content .tab-pane:nth-child(", i, ")').hide();"))
    }
  })
  # 
  observeEvent({
    lapply(1:num_tabs, function(i) {
      input[[paste0("switch_tab_", i)]]
    })
  }, {
    current_tab <- isolate({
      tab_selected <- i
      as.numeric(tab_selected)
    })

    if (!is.na(current_tab) && current_tab < num_tabs) {
      next_tab <- current_tab + 1
      runjs(paste("$('.nav-tabs li:nth-child(", next_tab, ")').show();"))
      runjs(paste("$('.tab-content .tab-pane:nth-child(", next_tab, ")').show();"))
    }
  })
}

shinyApp(ui, server)


Solution

  • You have multiple issues. First, you need to have useShinyjs() in the ui. Next, all five tabs were not even created, as tabsetPanel does not accept lists (named tabs in your case). Next, use updateTabsetPanel to switch tabs; you need session in the server function for that. I also added a previous tab, in case you wish to go back to previous tab. Try this

    library(shiny)
    library(shinyjs)
    
    ui <- fluidPage(
      useShinyjs(),
      mainPanel(
        uiOutput("tabs")
      )
    )
    
    server <- function(input, output, session) {
    
      num_tabs <- 5  # Define the number of tabs
    
      ### create 5 tabs
      output$tabs <- renderUI({
        do.call(tabsetPanel, c(id="tabs_content",
                               lapply(1:num_tabs, function(i) {
                                 tabPanel(title = paste("Tab", i), value = paste0("tab_", i),
                                          if (i>1) actionButton(inputId = paste0("prev_tab_", i), label = paste("Previous Tab")),
                                          if (i<5) actionButton(inputId = paste0("switch_tab_", i), label = paste("Next Tab"))  
                                 )
                               })
        ))
      })
    
      ###  Initially, hide tabs 2 thru 5
      observeEvent(input$tabs_content, {
        for (i in 2:num_tabs) {
          runjs(paste("$('.nav-tabs li:nth-child(", i, ")').hide();"))
          #runjs(paste("$('.tab-content .tab-pane:nth-child(", i, ")').hide();"))
        }
      }, once = TRUE)
    
      ### upon clicking on the actionButton, open the next tab
      lapply(1:(num_tabs-1), function(i) {
        observeEvent(input[[paste0("switch_tab_", i)]], {
          next_tab <- i+1
          runjs(paste("$('.nav-tabs li:nth-child(", next_tab, ")').show();"))
          updateTabsetPanel(session, "tabs_content", selected=paste0("tab_",next_tab) )
          runjs(paste("$('.nav-tabs li:nth-child(", i, ")').hide();"))
          #runjs(paste("$('.tab-content .tab-pane:nth-child(", next_tab, ")').show();"))
        }, ignoreInit = TRUE, ignoreNULL = TRUE)
      })
    
      lapply(2:num_tabs, function(i) {
        observeEvent(input[[paste0("prev_tab_", i)]], {
          prev_tab <- i-1
          runjs(paste("$('.nav-tabs li:nth-child(", prev_tab, ")').show();"))
          updateTabsetPanel(session, "tabs_content", selected=paste0("tab_",prev_tab) )
          runjs(paste("$('.nav-tabs li:nth-child(", i, ")').hide();"))
        }, ignoreInit = TRUE, ignoreNULL = TRUE)
      })
    }
    
    shinyApp(ui, server)