简体   繁体   中英

Dynamically add multiple tabBoxes to shinydashboard

I want to add a bunch of tabBoxes with panels programmatically. Unfortunately I can only find solutions adding panels not adding multiple boxes.

This code does not work ;-)

library(shinydashboard)

tabs.all <- list(
                  list(list(Title = "Tab1", Content = "Tab1 content"),
                     list(Title = "Tab2", Content = "Tab2 content"),
                     list(Title = "Tab3", Content = "Tab3 content")),

                list(list(Title = "Tab21", Content = "Tab21 content"),
                     list(Title = "Tab22", Content = "Tab22 content"),
                     list(Title = "Tab23", Content = "Tab23 content"))
                )


ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody(

    uiOutput("tabs")

  )
)

server <- function(input, output, session) {
  output$tabs <- renderUI({
    for (tabs.content in tabs.all){
      tabs <- lapply(1:length(tabs.content), function(i) tabPanel(tabs.content[[i]]$Title, tabs.content[[i]]$Content))
      do.call(tabBox, tabs)
    }
  })
}

shinyApp(ui, server)

Seems as though the magic disappears with the for-loop. Anything in there won't make it directly to the output.

This snippet does the trick as expected by accumulating the code in my_boxes :

 output$tabs <- renderUI({
    counter <- 1
    my_boxes <- ""

    for (tabs.content in tabs.all){
      counter <- counter +1
      tabs <- lapply(1:length(tabs.content), function(i) tabPanel(tabs.content[[i]]$Title, tabs.content[[i]]$Content))

      my_boxes <-paste0(my_boxes,do.call(tabBox, tabs)) 
    }
    HTML(my_boxes)
  })

This is old but basically you've got two major problems: Your data is very poorly organized, and you're overwriting your assignments each cycle of the loop.

Data Structure

r$> str(tabs.all)
List of 2
 $ :List of 3
  ..$ :List of 2
  .. ..$ Title  : chr "Tab1"        
  .. ..$ Content: chr "Tab1 content"
  ..$ :List of 2
  .. ..$ Title  : chr "Tab2"
  .. ..$ Content: chr "Tab2 content"
  ..$ :List of 2
  .. ..$ Title  : chr "Tab3"
  .. ..$ Content: chr "Tab3 content"
 $ :List of 3
  ..$ :List of 2
  .. ..$ Title  : chr "Tab21"
  .. ..$ Content: chr "Tab21 content"
  ..$ :List of 2
  .. ..$ Title  : chr "Tab22"
  .. ..$ Content: chr "Tab22 content"
  ..$ :List of 2
  .. ..$ Title  : chr "Tab23"
  .. ..$ Content: chr "Tab23 content"

You've got a list named tabs.all containing two lists. Each of those two lists contains
three unnamed lists. Each of those three unnamed lists contains another two named lists. And finally inside those final sets of two named lists you have a single character vector each. Your actual data is five layers down .

Lists aren't evil, they're just misused. Lists are best used to hold things that hold data, not to hold data themselves. Data like yours is perfect for a dataframe. You've got clearly defined variables to use as columns, for each row the items are related to each other, and everything "rectangles" nicely.

Assignment of output

The second big issue is you're overwriting the output of lapply every cycle of the loop. For explanation let's make a simplified example:

for (foo in seq(nrow(mtcars))){     #seq(nrow()) is safer than 1:X 
print(paste("cyl:",mtcars$cyl[foo],"disp:",mtcars$disp[foo],sep=" "))
}

Output:
[1] "cyl: 6 disp: 160"
[1] "cyl: 6 disp: 160"  
[1] "cyl: 4 disp: 108"  
[1] "cyl: 6 disp: 258"  

You can see that every cycle of the loop the output of paste() is a character vector of length 1. If you were to assign the output as in bar<-paste() then the contents of bar after the loop finishes would be only the data from the last row of mtcars .

The solution to this when using an explicit loop is to tell R you want to assign the output to a specific position, as in bar[loopcounter]<-paste() . This way every cycle of the loop you'll assign the output of your function to a specific position instead of overwriting the entire object (ie position 1) every time.

That's why your solution worked. You basically created an append() function. Every cycle of the loop you take the current my_boxes , append the output of the freshly overwritten tabs onto the end of it, and then reassign that as the new my_boxes .

A much easier solution

Putting all of that together though there's a much cleaner and easier way to do all of this:


library(shiny)
library(tidyverse)
library(shinydashboard)

tabs_all <- tribble(
  ~Title,  ~Content,
  "Tab1", "Tab1 Content",
  "Tab2", "Tab2 Content",
  "Tab3", "Tab3 Content",
  "Tab21", "Tab21 Content",
  "Tab22", "Tab22 Content",
  "Tab23", "Tab23 Content")


ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody(

    uiOutput("tabs")

  )
)

server <- function(input, output, session) {
  output$tabs <- renderUI({
    my_boxes <- list()

    for (rows in seq(nrow(tabs_all))){
      my_boxes[rows] <- tagList(
        tabPanel(title=tabs_all$Title[rows], tabs_all$Content[rows])
      )
    }

    do.call(tabBox, my_boxes)
  })
}

shinyApp(ui, server)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM