Examples of putting downloadHandler in a function?

I am building a complex dashboard with approximately 32 boxes showing different types of information. Some of the boxes display tables (rendered using knitr), some display plots (produced by ggplot). Regardless, each box gives users the option to download both the plot and a customized data set containing the plotted data (in a csv file).

I have individual downloadButtons and an instance of downloadHandler associated with each download. The code for these blocks follows the examples available in the shiny documentation. I'm not looking forward to maintaining 50 to 60 instances of the downloadHandler to manage all of the downloads.

Are there any examples of putting the downloadHandler in a wrapper function? Ideally, I'd like to pass an output parameter, a file name parameter and an output type parameter to the function.

Thanks in advance

This sounds like a good scenario for the use of a module function. I did a blogpost about using modules for this kind of thing recently. The example doesn't use downHandlers but you can incorporate any UI + Server combination to a module. Let me know if you have any questions!

3 Likes

I think @paul's point towards modules is definitely the right way to go. module documentation at shiny.rstudio.com is another place to explore modules, as well

1 Like

Thanks for the suggestion, Paul. I have created a function to create a downloadButton (called "downloadObjUI") and a function containing the downloadHandler (called "downloadObj"). One question I have is how to link the "downloadObj" and "downloadObjUI". How does one fire off downloadObj when a button created by downloadObjUI is clicked?

TIA

The inputs from the UI module will then be linked to the server logic in your downloadObj funciton, provided you created an ns <- NS(id) variable in your UI function and wrapped inputs/outputs in the ns() function.

In your case:

downloadObjUI <- function(id, ...) {
  ns <- NS(id)

  downloadButton(n("data_download"), label = "Download Data")
}

Will link to something like this in downloadObj:

downloadObj <- function(input, output, session, ...) {
  
  output$chart_download <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      write.csv(data, file)
    }
  )
}

So if you have a UI and accompanying server module setup like so, you can call the UI function within your UI code:

downloadObjUI(id = "download1", ...) 
# '...' denoting anythng else you need to pass to the module

Then in your server you call the server side module and pass the same id that you gave the UI module:

callModule(downloadObj, id = "download1", ...)

Let me know if that helps!

Hi Paul,
Thanks. This worked without a hitch.
Cheers--
AB

1 Like

I get an error when executing your code:

Error in n("data_download") : unused argument ("data_download")

In module:

downloadObjUI <- function(id) {
  ns <- NS(id)  
  downloadButton(n("data_download"), label = "Download Data")
}

downloadObj <- function(input, output, session,...) {
  
  output$plot1 <- renderPlot({plotfunction_1(...)})
  
  output$chart_download <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".png", sep="")
    },
    content = function(file) {
      png(reactive({output$plot1}), file)
    }
  )
}

In app:

callModule(downloadObj,"downloadplot1",...)

downloadObjUI(id = "downloadplot1")

sorry that's a typo! it should be ns("data_download") not n.

It should be downloadButton(ns("chart_download"), label = "Download Data") in downloadObjUI.

1 Like

Thanks

I do not get the error anymore, but when clicking the button, the dialog window opens, but there's no file to be saved.

I think that may be because downloads don't work in the RStudio viewer. Try opening the app in a browser like chrome to see if that solves the issue.

If not, here's a full reproducible example of a working download button module. Remember you have to pass the data you want to download as an argument to the module server function:

library(shiny)

downloadObjUI <- function(id) {
  ns <- NS(id)
  
  downloadButton(ns("data_download"), label = "Download Data", class = "btn-primary")
}

downloadObj <- function(input, output, session, data) {
  
  output$data_download <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      write.csv(data(), file) # add parentheses to data arg if reactive
    }
  )
}

ui <- fluidPage(
  selectInput("filter", "Cylinders", choices = unique(mtcars$cyl)),
  tableOutput("table"),
  downloadObjUI(id = "download1")
)

server <- function(input, output) {
  # filtered dataset
  my_data <- reactive({ mtcars %>% filter(cyl == input$filter) })
  
  output$table <- renderTable({ my_data() })
  
  callModule(downloadObj, id = "download1", data = my_data)
}

shinyApp(ui, server)
3 Likes

Hi All,

I am working on a very similar shiny app. But I would like to access all of my reactive values in a .rmd file where I would further modify them. I created the downloadhandler module but it cannot see the reactive values in the app environment. Do you have any tips on how to pass all the reactive to the downloadhandler module?

Thanks

You should be able to pass reactives from the main app to your download module as function arguments and then pass them as paramaters to the .Rmd file. Something like:

downloadObj <- function(input, output, session, reactive_1, reactive_2) {
  
  output$report_download <- downloadHandler(
    filename = function() {
      paste0("report-", Sys.Date(), ".html")
    },
    content = function(file) {
        # Set up parameters to pass to Rmd document
        params <- list(param_1 = reactive_1(), param_2 = reactive_2())

        # Knit the document, passing in the `params` list
        rmarkdown::render(
          "report.Rmd", output_file = file, params = params
        )
    }
  )
}
1 Like

Hi!

Thanks for the answer, it works now! In the .rmd file I am referring to the reactive value as reactive_1() instead of param_1 or by the name of the reactive value object outside of the module (in the app environment).

1 Like