Generating dynamic UI in Shiny with loops


#1

I’m looking to generate dynamic output in Shiny based on individual rows of a data frame (which will be a reactive data frame in the future). The goal is to display some columns of the data frame as text (e.g. a header of a section) while other columns will be grouped together in a table. Each row will have it’s own “section” in the UI. The reprex below loops over a data frame and should generate tables based on a column and output those tables. However, the UI does not render anything. Any thoughts on a better way to accomplish this?

library(readr)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
library(shiny)

scoreTable <- tribble(
  ~x1, ~x2, ~x3, ~x4, ~x5,
  #--|--|----
  "a", 2, 3.6, 10, 20,
  "b", 1, 8.5, 15, 30,
  "c", 4, 9.7, 22, 45
)

ui <- fluidPage(
  uiOutput("myUI")
)

server <-  function(input, output) {
  
  output$myUI <- renderUI({
    
    for(i in 1:nrow(scoreTable)) {
      output[[paste0(scoreTable[i, "x1"],
                     scoreTable[i, "x2"])]] <- renderTable({
                       
                       scoreTable[i, "x1"]
                       
                     })
      
      tableOutput(paste0(scoreTable[i, "x1"],
                         scoreTable[i, "x2"]))
      
    }
  })
}

shinyApp(ui = ui, server = server)

#2

this is a solution (but @barbara may be more biased towards the shiny module solution)

library(shiny)
library(purrr)
library(dplyr)

ui <- shiny::fluidPage(
  shiny::uiOutput("myUI")
)

server <-  function(input, output,session) {
  
  scoreTable <- shiny::reactive({
    dplyr::tribble(
      ~x1, ~x2, ~x3, ~x4, ~x5,
      #--|--|----
      "a", 2, 3.6, 10, 20,
      "b", 1, 8.5, 15, 30,
      "c", 4, 9.7, 22, 45
    )
  })
  
  output$myUI <- shiny::renderUI({ 
    do.call(
      what = shiny::tabsetPanel,
      args= purrr::map(scoreTable()$x1,.f = function(nm){
          shiny::tabPanel(title=nm,
                          shiny::renderTable({
                            scoreTable()%>%dplyr::filter(x1==nm)
                          }))
        })
      )
    })
}

shiny::shinyApp(ui = ui, server = server)


#3

Thanks @yonicd for the feedback. After playing around with it a bit more I think I got the desired output I needed. I don’t think my description was as clear as it was in my head but the code I developed to accomplish this is below. Basically the createUI() function can generate the UI output based on each row of the data frame.

library(readr)
library(dplyr)
library(shiny)

scoreTable <- tribble(
  ~x1, ~x2, ~x3, ~x4, ~x5,
  #--|--|----
  "a", 2, 3.6, 10, 20,
  "b", 1, 8.5, 15, 30,
  "c", 4, 9.7, 22, 45
)

ui <- fluidPage(
  uiOutput("myUI")
)

server <-  function(input, output) {
  
  createUI <- function(dfID, df) {
    
    div(
      h1(paste0(df$x1, " - ", df$x2)),
      
      output[[as.character(dfID)]] <- renderTable(df %>%
                                                    select(x3, x4, x5) %>%
                                                    t(),
                                                  rownames = TRUE,
                                                  colnames = FALSE)
    )
  }
  
  output$myUI <- renderUI({
    
    w <- lapply(seq_len(nrow(scoreTable)), function(i) {
      createUI(i, scoreTable[i, ])
    })
    
    do.call(fluidRow, w)
  })
}

shinyApp(ui = ui, server = server)