1

I succeed to make dynamic tabPanels thanks to K. Rohde solution (Dynamically creating tabs with plots in shiny without re-creating existing tabs).

But, when i tried to import this in a shinydashboard layout, I observed a strange behavior of the dynamically created tabPanel : when I select one, the page goes empty excepts for the content I specified inside the new panel.

I don't understand why... It seems that new tabs don't inherit the belonging to others layout elements (it should be true without using shinydashboard to... perhaps?), and hide them (!?). When I click on another tabItem, and come back, everything look like before I clicked on the "dynamic tab".

It's not easy to explain, so here's a repoducible example which is just a transposition of K. Rhode example in a shinydashboard layout :

library(shiny)
library(shinydashboard)

ui <- dashboardPage(skin = "green",
 dashboardHeader(title = 'Dynamic Tabs'),
 dashboardSidebar(
  sidebarMenu(id = "sidebarmenu",
   menuItem("Page1", tabName = "Page1",  icon = icon("map"), selected = TRUE),
   # just to show that coming back to page 1 from page 2 recall the good layout :
   menuItem("Page2", tabName = "Page2",  icon = icon("dashboard"), selected = FALSE) 
  )
 ),
 dashboardBody(
  tags$head(tags$script(HTML("
   /* In coherence with the original Shiny way, tab names are created with random numbers. 
   To avoid duplicate IDs, we collect all generated IDs.  */
   var hrefCollection = [];
   
   Shiny.addCustomMessageHandler('addTabToTabset', function(message){
   var hrefCodes = [];
   /* Getting the right tabsetPanel */
   var tabsetTarget = document.getElementById(message.tabsetName);
   
   /* Iterating through all Panel elements */
   for(var i = 0; i < message.titles.length; i++){
   /* Creating 6-digit tab ID and check, whether it was already assigned. */
   do {
   hrefCodes[i] = Math.floor(Math.random()*100000);
   } 
   while(hrefCollection.indexOf(hrefCodes[i]) != -1);
   hrefCollection = hrefCollection.concat(hrefCodes[i]);
   
   /* Creating node in the navigation bar */
   var navNode = document.createElement('li');
   var linkNode = document.createElement('a');
   
   linkNode.appendChild(document.createTextNode(message.titles[i]));
   linkNode.setAttribute('data-toggle', 'tab');
   linkNode.setAttribute('data-value', message.titles[i]);
   linkNode.setAttribute('href', '#tab-' + hrefCodes[i]);
   
   navNode.appendChild(linkNode);
   tabsetTarget.appendChild(navNode);
   };
   
   /* Move the tabs content to where they are normally stored. Using timeout, because
   it can take some 20-50 millis until the elements are created. */ 
   setTimeout(function(){
   var creationPool = document.getElementById('creationPool').childNodes;
   var tabContainerTarget = document.getElementsByClassName('tab-content')[0];
   
   /* Again iterate through all Panels. */
   for(var i = 0; i < creationPool.length; i++){
   var tabContent = creationPool[i];
   tabContent.setAttribute('id', 'tab-' + hrefCodes[i]);
   
   tabContainerTarget.appendChild(tabContent);
   };
   }, 100);
   });
   "))),
  tabItems(
   tabItem(tabName = "Page1",
    tabsetPanel(id = "mainTabset", 
     tabPanel("InitialPanel1", "Some Text here to show this is InitialPanel1", 
      actionButton("goCreate", "Go create a new Tab!"),
      textOutput("creationInfo")
     ),
     tabPanel("InitialPanel2", "Some Text here to show this is InitialPanel2 and not some other Panel")
    )
   ),
   # Important! : 'Freshly baked' tabs first enter here.
   uiOutput("creationPool", style = "display: none;")
   # End Important
   ,
   tabItem(tabName = "Page2", "Go back to page 1...")
  )
 )
)

server <- function(input, output, session){
 
 # Important! : creationPool should be hidden to avoid elements flashing before they are moved.
 #              But hidden elements are ignored by shiny, unless this option below is set.
 output$creationPool <- renderUI({})
 outputOptions(output, "creationPool", suspendWhenHidden = FALSE)
 # End Important
 
 # Important! : This is the make-easy wrapper for adding new tabPanels.
 addTabToTabset <- function(Panels, tabsetName){
  titles <- lapply(Panels, function(Panel){return(Panel$attribs$title)})
  Panels <- lapply(Panels, function(Panel){Panel$attribs$title <- NULL; return(Panel)})
  
  output$creationPool <- renderUI({Panels})
  session$sendCustomMessage(type = "addTabToTabset", message = list(titles = titles, tabsetName = tabsetName))
 }
 # End Important 
 
 # From here: Just for demonstration
 output$creationInfo <- renderText({
  paste0("The next tab will be named NewTab", input$goCreate + 1)
 })
 
 observeEvent(input$goCreate, {
  nr <- input$goCreate
  
  newTabPanels <- list(
   tabPanel(paste0("NewTab", nr), 
    actionButton(paste0("Button", nr), "Some new button!"), 
    textOutput(paste0("Text", nr))
   ), 
   tabPanel(paste0("AlsoNewTab", nr), sliderInput(paste0("Slider", nr), label = NULL, min = 0, max = 1, value = 1))
  )
  
  output[[paste0("Text", nr)]] <- renderText({
   if(input[[paste0("Button", nr)]] == 0){
    "Try pushing this button!"
   } else {
    paste("Button number", nr , "works!")
   }
  })
  
  addTabToTabset(newTabPanels, "mainTabset")
   })
}

shinyApp(ui, server)

Cheers.

Community
  • 1
  • 1
Alex D.
  • 11
  • 2

0 Answers0