Search code examples
javascriptrdynamicshinytabpanel

R Shiny: Dynamic tabs within multiple navbarPage tabPanels


I'm creating a Shiny app with multiple tabPanels using navbarPage(). In each separate tabPanel, you choose a few input parameters, click on a button and it stores the output in a separate tab within that tabPanel. I used the script of K.Rohde to create dynamic tabs (Note that I left their comments in the example script I made below).

I made an example with two tabPanels: Summary and Plot. 'Summary' takes 4 letters and returns text output. 'Plot' takes a number of observations and returns a histogram. Each result is stored in a separate tab within 'Summary' and 'Plot'. The tabPanels work perfectly when I save them as a separate Shiny App, but when I try to merge them into one App they don't work anymore. In this example, the tabPanel "Plot" doesn't work anymore. Sometimes it even returns the output of the 'Plot' tabPanel in the 'Summary' tabPanel.

I tried changing the (Javascript) code from K.Rohde by making every variable unique: each variable in tabPanel 'Summary' ends with _sum and each variable in tabPanel 'Plot' ends with _plot. However, this doesn't seem to fix my problem.

You can copy-paste the code below to reproduce my problem. I appreciate any help!

ui:

ui <- navbarPage("Shiny",

                 # *JavaScript functionality to add the Tabs*
                 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_sum = [];

                                            Shiny.addCustomMessageHandler('addTabToTabset_sum', function(message_sum){
                                            var hrefCodes_sum = [];
                                            /* Getting the right tabsetPanel */
                                            var tabsetTarget_sum = document.getElementById(message_sum.tabsetName_sum);

                                            /* Iterating through all Panel elements */
                                            for(var i = 0; i < message_sum.titles.length; i++){
                                            /* Creating 6-digit tab ID and check, whether it was already assigned. */
                                            do {
                                            hrefCodes_sum[i] = Math.floor(Math.random()*100000);
                                            } 
                                            while(hrefCollection_sum.indexOf(hrefCodes_sum[i]) != -1);
                                            hrefCollection_sum = hrefCollection_sum.concat(hrefCodes_sum[i]);

                                            /* Creating node in the navigation bar */
                                            var navNode_sum = document.createElement('li');
                                            var linkNode_sum = document.createElement('a');

                                            linkNode_sum.appendChild(document.createTextNode(message_sum.titles[i]));
                                            linkNode_sum.setAttribute('data-toggle', 'tab');
                                            linkNode_sum.setAttribute('data-value', message_sum.titles[i]);
                                            linkNode_sum.setAttribute('href', '#tab-' + hrefCodes_sum[i]);

                                            navNode_sum.appendChild(linkNode_sum);
                                            tabsetTarget_sum.appendChild(navNode_sum);
                                            };

                                            /* 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_sum = document.getElementById('creationPool_sum').childNodes;
                                            var tabContainerTarget_sum = document.getElementsByClassName('tab-content')[1];

                                            /* Again iterate through all Panels. */
                                            for(var i = 0; i < creationPool_sum.length; i++){
                                            var tabContent_sum = creationPool_sum[i];
                                            tabContent_sum.setAttribute('id', 'tab-' + hrefCodes_sum[i]);

                                            tabContainerTarget_sum.appendChild(tabContent_sum);
                                            };
                                            }, 100);
                                            });
                                            "))),
                 # End Important
                 tabPanel("Summary",
                          sidebarLayout(
                            sidebarPanel(width = 4,
                                         selectInput(inputId = "choice_1_sum", label = "First choice:",
                                                     choices = LETTERS, selected = "H", multiple = FALSE),
                                         selectInput(inputId = "choice_2_sum", label = "Second choice:",
                                                     choices = LETTERS, selected = "E", multiple = FALSE),
                                         selectInput(inputId = "choice_3_sum", label = "Third choice:",
                                                     choices = LETTERS, selected = "L", multiple = FALSE),
                                         selectInput(inputId = "choice_4_sum", label = "Fourth choice:",
                                                     choices = LETTERS, selected = "P", multiple = FALSE),
                                         actionButton("goStat", "Go create a new Tab!")
                            ), 
                            mainPanel(
                              tabsetPanel(id = "mainTabset_sum",
                                          tabPanel("InitialPanel1_sum", "Some text here to show this is InitialPanel1",
                                                   textOutput("creationInfo_sum"),
                                                   # Important! : 'Freshly baked' tabs first enter here.
                                                   uiOutput("creationPool_sum", style = "display: none;")
                                                   # End Important
                                          )
                              )
                            )
                          )
                 ),

                 # *JavaScript functionality to add the Tabs*
                 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_plot = [];

                                            Shiny.addCustomMessageHandler('addTabToTabset_plot', function(message_plot){
                                            var hrefCodes_plot = [];
                                            /* Getting the right tabsetPanel */
                                            var tabsetTarget_plot = document.getElementById(message_plot.tabsetName_plot);

                                            /* Iterating through all Panel elements */
                                            for(var i = 0; i < message_plot.titles.length; i++){
                                            /* Creating 6-digit tab ID and check, whether it was already assigned. */
                                            do {
                                            hrefCodes_plot[i] = Math.floor(Math.random()*100000);
                                            } 
                                            while(hrefCollection_plot.indexOf(hrefCodes_plot[i]) != -1);
                                            hrefCollection_plot = hrefCollection_plot.concat(hrefCodes_plot[i]);

                                            /* Creating node in the navigation bar */
                                            var navNode_plot = document.createElement('li');
                                            var linkNode_plot = document.createElement('a');

                                            linkNode_plot.appendChild(document.createTextNode(message_plot.titles[i]));
                                            linkNode_plot.setAttribute('data-toggle', 'tab');
                                            linkNode_plot.setAttribute('data-value', message_plot.titles[i]);
                                            linkNode_plot.setAttribute('href', '#tab-' + hrefCodes_plot[i]);

                                            navNode_plot.appendChild(linkNode_plot);
                                            tabsetTarget_plot.appendChild(navNode_plot);
                                            };

                                            /* 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_plot = document.getElementById('creationPool_plot').childNodes;
                                            var tabContainerTarget_plot = document.getElementsByClassName('tab-content')[1];

                                            /* Again iterate through all Panels. */
                                            for(var i = 0; i < creationPool_plot.length; i++){
                                            var tabContent_plot = creationPool_plot[i];
                                            tabContent_plot.setAttribute('id', 'tab-' + hrefCodes_plot[i]);

                                            tabContainerTarget_plot.appendChild(tabContent_plot);
                                            };
                                            }, 100);
                                            });
                                            "))),
                 # End Important
                 tabPanel("Plot",
                          sidebarLayout(
                            sidebarPanel(width = 4,
                                         numericInput(inputId = "obs_plot", "Number of observations:", value = 100),
                                         actionButton("goPlot", "Create a new Tab!")
                            ), 
                            mainPanel(
                              tabsetPanel(id = "mainTabset_plot",
                                          tabPanel("InitialPanel1_plot", "Some text here to show this is InitialPanel1",
                                                   textOutput("creationInfo_plot"),
                                                   # Important! : 'Freshly baked' tabs first enter here.
                                                   uiOutput("creationPool_plot", style = "display: none;")
                                                   # End Important
                                          )
                              )
                            )
                          )
                 )
)

server:

server <- function(input, output, session){

  #################
  #  Summary Tab  #
  #################

  # 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_sum <- renderUI({})
  outputOptions(output, "creationPool_sum", suspendWhenHidden = FALSE)
  # End Important

  # Important! : This is the make-easy wrapper for adding new tabPanels.
  addTabToTabset_sum <- function(Panels, tabsetName_sum){
    titles <- lapply(Panels, function(Panel){return(Panel$attribs$title)})
    Panels <- lapply(Panels, function(Panel){Panel$attribs$title <- NULL; return(Panel)})

    output$creationPool_sum <- renderUI({Panels})
    session$sendCustomMessage(type = "addTabToTabset_sum", message = list(titles = titles, tabsetName_sum = tabsetName_sum))
  }
  # End Important 

  # From here: Just for demonstration
  output$creationInfo_sum <- renderText({
    paste0("The next tab will be named: Results ", input$goStat + 1)
  })

  observeEvent(input$goStat, {
    nr <- input$goStat

    newTabPanels_sum <- list(
      tabPanel(paste0("NewTab ", nr),

               htmlOutput(paste0("Html_text_sum", nr))
      )
    )

    output[[paste0("Html_text_sum", nr)]] <- renderText({
      paste("<strong>", "Summary:", "</strong>", "<br>",
            "You chose the following letters:", isolate(input$choice_1_sum), isolate(input$choice_2_sum), isolate(input$choice_3_sum), isolate(input$choice_4_sum), "." ,"<br>",
            "Thank you for helping me!")
    })

    addTabToTabset_sum(newTabPanels_sum, "mainTabset_sum")
  })

  #################
  #   Plot Tab    #
  #################

  # 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_plot <- renderUI({})
  outputOptions(output, "creationPool_plot", suspendWhenHidden = FALSE)
  # End Important

  # Important! : This is the make-easy wrapper for adding new tabPanels.
  addTabToTabset_plot <- function(Panels, tabsetName_plot){
    titles <- lapply(Panels, function(Panel){return(Panel$attribs$title)})
    Panels <- lapply(Panels, function(Panel){Panel$attribs$title <- NULL; return(Panel)})

    output$creationPool_plot <- renderUI({Panels})
    session$sendCustomMessage(type = "addTabToTabset_plot", message = list(titles = titles, tabsetName_plot = tabsetName_plot))
  }
  # End Important 

  # From here: Just for demonstration
  output$creationInfo_plot <- renderText({
    paste0("The next tab will be named: Results ", input$goPlot + 1)
  })

  observeEvent(input$goPlot, {
    nr <- input$goPlot

    newTabPanels_plot <- list(
      tabPanel(paste0("NewTab ", nr),

               plotOutput(paste0("plot", nr))
      )
    )

    output[[paste0("plot", nr)]] <- renderPlot({
      hist(runif(isolate(input$obs_plot)))
    })

    addTabToTabset_plot(newTabPanels_plot, "mainTabset_plot")
  })
}

Solution

  • Honestly, I don't know why your code does not work. However, I would suggest to use a somewhat different approach. If I get you right, with appendTab the following code should exactly produce what you're after.

    ui <- 
    fluidPage(
    navbarPage("Shiny",
    
    tabPanel("summary",
      sidebarLayout(
            sidebarPanel(width = 4,
                                             selectInput(inputId = "choice_1_sum", label = "First choice:",
                                                         choices = LETTERS, selected = "H", multiple = FALSE),
                                             selectInput(inputId = "choice_2_sum", label = "Second choice:",
                                                         choices = LETTERS, selected = "E", multiple = FALSE),
                                             selectInput(inputId = "choice_3_sum", label = "Third choice:",
                                                         choices = LETTERS, selected = "L", multiple = FALSE),
                                             selectInput(inputId = "choice_4_sum", label = "Fourth choice:",
                                                         choices = LETTERS, selected = "P", multiple = FALSE),
                                             actionButton("goStat", "Go create a new Tab!")
                                ), 
                                mainPanel(
                                  tabsetPanel(id = "mainTabset_sum",
                                              tabPanel("InitialPanel1_sum", "Some text here to show this is InitialPanel1",
                                                       textOutput("creationInfo_sum"),
                                                       uiOutput("creationPool_sum", style = "display: none;")
                                                       )
                                              )
                                          )
    
                     )
              ),
    
                     tabPanel("Plot",
                              sidebarLayout(
                                sidebarPanel(width = 4,
                                             numericInput(inputId = "obs_plot", "Number of observations:", value = 100),
                                             actionButton("goPlot", "Create a new Tab!")
                                ), 
                                mainPanel(
                                  tabsetPanel(id = "mainTabset_plot",
                                              tabPanel("InitialPanel1_plot", "Some text here to show this is InitialPanel1",
                                                       textOutput("creationInfo_plot"),
                                                       # Important! : 'Freshly baked' tabs first enter here.
                                                       uiOutput("creationPool_plot", style = "display: none;")
                                                       # End Important
                                              )
                                  )
                                )
                              )
                     )
    
    
    )
    )
    
    
    server <- function(input, output, session) {
    
    observeEvent(input$goStat, {
    
        appendTab(inputId = "mainTabset_sum",
                            select = T,
                tabPanel(paste0("newtab", input$goStat), htmlOutput(paste0("text", input$goStat))
                                 )
        )
    
            output[[paste0("text", input$goStat)]] <- renderText({
          paste("<strong>", "Summary:", "</strong>", "<br>",
                "You chose the following letters:", isolate(input$choice_1_sum), isolate(input$choice_2_sum), isolate(input$choice_3_sum), isolate(input$choice_4_sum), "." ,"<br>",
                "Thank you for helping me!")
        })
    
    
    })
    
    
    
    
    
    observeEvent(input$goPlot, {
    
    appendTab(inputId = "mainTabset_plot",
                select = T,
          tabPanel(paste0("newplot", input$goPlot), plotOutput(paste0("plot", input$goPlot)))
                         )
    
    output[[paste0("plot", input$goPlot)]] <- renderPlot({
          hist(runif(isolate(input$obs_plot)))
        })
    
      })    
    
    
    }
    
    shinyApp(ui, server)