Download report with tagList

Hi There,

I have this shiny app with a tagList that I'd like to access when I download a Report:

shinyApp(
 ui = fluidPage(
   sliderInput("slider", "Slider", 1, 100, 50),
   textInput("FN", label = h3("File name:"), 
             value = "File name"),
   textInput("YN", label = h3("Your name:"), 
             value = "Your name"),
   radioButtons('format', 'Document format', c('PDF', 'Word'),
                inline = TRUE),
   downloadButton("report", "Generate report"),
   uiOutput("RanInput")

 ),
 
 server = function(input, output) {
   
   R <- reactive({
     sapply(1:5, function(i){paste0("input$Rand",i)})
   })
   
   output$RanInput <- renderUI({

     output = tagList()
     G = letters[1:5]
     
     for(i in seq_along(1:5)){
       output[[i]] = tagList()
       output[[i]][[1]] = hr(style="height:5px;background-color:blue")
       output[[i]][[2]] = G[i]
       output[[i]][[3]] = br()
       output[[i]][[4]] = selectInput(R()[i], "Treatment or control:",
                                      c("Treatment" = "1", "Control" = "0"))
       
     } ## for loop
     
     output
   })
   
   output$report <-  downloadHandler(
     ## Making the filename for the report here. Using two different 
     ## extensions for the file
     filename = function() {
       paste(input$FN, sep = '.', switch(input$format, PDF = 'pdf', 
                                         Word = 'docx'))
     },
     
     content = function(file) {
       ## Copy the report file to a temporary directory before processing it, in
       ## case we don't have write permissions to the current working dir (which
       ## can happen when deployed).
       tempReport <- file.path(tempdir(), "report.Rmd")
       file.copy("report.Rmd", tempReport, overwrite = TRUE)
       
       # Set up parameters to pass to Rmd document
       params <- list(n = input$slider,
                      R = R())
       
       # Knit the document, passing in the `params` list, and eval it in a
       # child of the global environment (this isolates the code in the document
       # from the code in this app).
       library(rmarkdown)
       rmarkdown::render(tempReport, 
                         switch(input$format,
                                PDF = pdf_document(), Word = word_document()),
                         output_file = file, params = params,
                         envir = new.env(parent = globalenv())
       )
     }
   )
   
 }
)

and the report file is as follows:

---
title: "Dynamic report"
output: html_document
params:
  n: NA
  R: NA
---

     A plot of `params$n` random points.

```{r}
plot(rnorm(params$n), rnorm(params$n))
\```

     Playing with R

```{r}
(data.frame(params$R))
sum(as.numeric(params$R))
\```

The problem I'm having is that the stupid f#@*#%ng pdf file has this as the output:

(data.frame(params$R))
params.R
1 input$Rand1
2 input$Rand2
3 input$Rand3
4 input$Rand4
5 input$Rand5

and

sum(as.numeric(params$R))
Warning: NAs introduced by coercion
[1] NA

Which means that it's not picking up the values. There must be a way around this, but I've not got a clue. Computers are so stupid, luckily we have each other.

gwynn

What's the purpose of reactive expression of R?

It's a bad idea to use library call inside your content function call, though this may not relate to your current problem.

Thanks for your comment.

I suppose you could discuss that with the author of this RStudio example.

It was my first attempt at a work around. If you know how to get it to work let me know.

I don't like that library usage but it may not hurt in this case, so let's ignore that for now.

As I said, I don't know the purpose of that expression, so I cannot let you know how to get it to work. Sorry.

  R <- reactive({
     sapply(1:5, function(i){paste0("input$Rand",i)})
   })

This produces a vector of strings like "input$Rand1", "input$Rand2" etc, which are passed in as input IDs to the selectInputs later on. The corresponding input values would then be input[["input$Rand1"]] and input[["input$Rand2"]], probably not what you intended.

Later on, these same input IDs are passed into the Rmd doc as-is, which explains the strange output. You'll need to get the actual input values from input before passing them in to the doc.

Thanks for your suggestion.

That makes sense, so I updated it:

server = function(input, output) {
    
    R <- reactive({
      sapply(1:5, function(i){paste0("Rand",i)})
    })
    
    RI <- reactive({
      sapply(1:5, function(i){paste0("input$Rand",i)})
    })
    
    output$RanInput <- renderUI({

      output = tagList()
      G = letters[1:5]
      
      for(i in seq_along(1:5)){
        output[[i]] = tagList()
        output[[i]][[1]] = hr(style="height:5px;background-color:blue")
        output[[i]][[2]] = G[i]
        output[[i]][[3]] = br()
        output[[i]][[4]] = selectInput(R()[i], "Treatment or control:",
                                       c("Treatment" = "1", "Control" = "0"))
        
      } ## for loop
      
      output
    })
    
    output$report <-  downloadHandler(
      ## Making the filename for the report here. Using two different 
      ## extensions for the file
      filename = function() {
        paste(input$FN, sep = '.', switch(input$format, PDF = 'pdf', 
                                          Word = 'docx'))
      },
      
      content = function(file) {
        ## Copy the report file to a temporary directory before processing it, in
        ## case we don't have write permissions to the current working dir (which
        ## can happen when deployed).
        tempReport <- file.path(tempdir(), "report.Rmd")
        file.copy("report.Rmd", tempReport, overwrite = TRUE)
        
        # Set up parameters to pass to Rmd document
        params <- list(n = input$slider,
                       R = RI())
        
        # Knit the document, passing in the `params` list, and eval it in a
        # child of the global environment (this isolates the code in the document
        # from the code in this app).
        library(rmarkdown)
        rmarkdown::render(tempReport, 
                          switch(input$format,
                                 PDF = pdf_document(), Word = word_document()),
                          output_file = file, params = params,
                          envir = new.env(parent = globalenv())
        )
      }
    )
    
  }

But the above coding doesn't transfer values to the .Rmd file.

I'm not sure how to accomplish this...

Thanks again

I tried another approach using an interactive document but got this error:
Error : Can't call runApp() from within runApp(). If your ,application code contains runApp(), please remove it.

There must be a way to do this...

A quick way to grab a series of input values based on their name is to utilize one of the apply functions or the map functions in the purrr package. If you made this change you will get the values for those treatment/control selections:

# Set up parameters to pass to Rmd document
params <- list(n = input$slider,
               R = sapply(R(), function(x) input[[x]]))

You don't need the RI() reactive since this already knows to go to the input slots.

1 Like

Thanks for your help! I knew it wasn't hard, I just couldn't get it.

So instead of using input$Rand1 I needed to use input[[Rand1]]? Looking at my app I've got both input[[Rand1]] and isolate({input$Rand1}). Can you explain the difference to me? Do I just use $ with the isolate function? Just curious.

I've never used the purr package but I've looked at the cheat sheet.

Wait!! Is it b/c it's a tagList?? It's a list so I've got to index it with input[[Rand1]], right?? I think I got it!!

The simplest things are so difficult!!