3

I'm trying to make a reactive data table in R Shiny that has a button you can press to compile an RMarkdown document. Ultimately, I'm trying to combine the solutions from these two links: R Shiny: Handle Action Buttons in Data Table and https://shiny.rstudio.com/articles/generating-reports.html. Here is what I have so far:

library(shiny)
library(shinyjs)
library(DT)

shinyApp(
  ui <- fluidPage(
    DT::dataTableOutput("data")
  ),

  server <- function(input, output) {

    useShinyjs()

    shinyInput <- function(FUN, len, id, ...) {
      inputs <- character(len)
      for (i in seq_len(len)) {
        inputs[i] <- as.character(FUN(paste0(id, i), ...))
      }
      inputs
    }
    
    df <- reactiveValues(data = data.frame(
  
      Portfolio = c('Column1', 'Column2'),
      Option_1 = shinyInput(downloadButton, 2, 'compile_', label = "Compile Document", onclick = 'Shiny.onInputChange(\"compile_document\",  this.id)' ),
      stringsAsFactors = FALSE,
      row.names = 1:2
    ))


    output$data <- DT::renderDataTable(
      df$data, server = FALSE, escape = FALSE, selection = 'none', filter='top'
    )

    output$compile_document <- downloadHandler(
      filename = "report.html",
      content = function(file) {

        tempReport <- file.path(tempdir(), "report.Rmd")
        file.copy("report.Rmd", tempReport, overwrite = TRUE)

        params <- list(n = input$slider)

        rmarkdown::render(tempReport, output_file = file,
                      params = params,
                      envir = new.env(parent = globalenv())
        )
      }
    )
  }
)

Here is the RMarkdown document I'd like to compile:

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

```{r}
# The `params` object is available in the document.
params$n
```

A plot of `params$n` random points.

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

The pieces all seem to be there, but I can't connect the "Compile Document" button to the download handler.

Waldi
  • 39,242
  • 6
  • 30
  • 78
Kevin Gregory
  • 89
  • 1
  • 11
  • Do you want the document compiling specific for every row or for the complete table? – starja Jul 20 '20 at 17:18
  • Yes, if that is possible. I'd like to have multiple rows and each compile button compiling a separate document. – Kevin Gregory Jul 20 '20 at 19:24
  • 1
    A quite interesting problem. The answer you linked generates buttons in every row of the df which then uses javascript to update the value of one central input value, to which an observeEvent listens. This doesn't work for `downloadHandler`, but I haven't quite figured out yet how `downloadHandler` works – starja Jul 20 '20 at 21:24
  • I'm not married to the idea of it being downloadHandler. Could it be done easily with an actionButton? – Kevin Gregory Jul 20 '20 at 21:26

1 Answers1

2

Here is a way that does not use downloadHandler.

library(shiny)
library(DT)
library(base64enc)
library(rmarkdown)

js <- '
Shiny.addCustomMessageHandler("download", function(b64){
  const a = document.createElement("a");
  document.body.append(a);
  a.download = "report.docx";
  a.href = b64;
  a.click();
  a.remove();
})
'

buttonHTML <- function(i){
  as.character(
    actionButton(
      paste0("button_", i), label = "Report", 
      onclick = sprintf("Shiny.setInputValue('button', %d);", i)           
    )
  )
}

dat <- data.frame(
  PortFolio = c("Column 1", "Column 2")
)
dat$Action <- sapply(1:nrow(dat), buttonHTML)


ui <- fluidPage(
  tags$head(tags$script(HTML(js))),
  br(),
  sliderInput("slider", "Sample size", min = 10, max = 50, value = 20),
  br(),
  DTOutput("dtable")
)


server <- function(input, output, session){
  
  output[["dtable"]] <- renderDT({
    datatable(dat, escape = -ncol(dat)-1)
  })
  
  observeEvent(input[["button"]], {
    showNotification("Creating report...", type = "message")
    tmpReport <- tempfile(fileext = ".Rmd")
    file.copy("report.Rmd", tmpReport)
    outfile <- file.path(tempdir(), "report.html")
    render(tmpReport, output_file = outfile, 
           params = list(
             data = dat[input[["button"]], -ncol(dat)],
             n = input[["slider"]]
           )
    )
    b64 <- dataURI(
      file = outfile, 
      mime = "text/html"
    )
    session$sendCustomMessage("download", b64)
  })
  
}

shinyApp(ui, server)

The rmd file:

---
title: "Dynamic report"
output: html_document
params:
  data: "x"
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

Row contents:

```{r}
params$data
```

A plot of `params$n` random points:

```{r}
plot(rnorm(params$n), rnorm(params$n))
```
Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225
  • is there a way to have two columns of buttons? I created a separate buttonHTML_2 function that changes the button identifier, and I created a `dat$Action_2 <- sapply(1:nrow(dat), buttonHTML_2)` column, but when I do that the first button doesn't render anymore. It just ends up being a long strange string of text. – Kevin Gregory Jul 23 '20 at 16:32
  • 1
    @KevinGregory You have to change the `escape` option accordingly: `escape = -c(ncol(dat), ncol(dat)+1)` – Stéphane Laurent Jul 23 '20 at 16:48