Search code examples
rshinydownloadanonymous-function

Capture the for-loop iteration value to create anonymous function for dynamically generated downloadHandler


I'm developing an R Shiny App building a list of graph and I want each data to be downloadable by a click on a button below each graph.

My problem is that the anonymous function created to handle the downloadHandler content args is interpreted only when my button is clicked, and I can't retrieve which button the user pressed on to dispatch the right data to download.

plot_ids is a list of plot_id ["plot_1","plot_2",...]

plot_data is a named list of data ["plot_1"=tibble(),"plot_2"=tibble(),...]

for (plot_id in plot_ids) {
  output[[plot_id]] <- downloadHandler(
      filename = function() {
        paste0("custom_export_", plot_id, ".csv")
      },
      content = function(file){
          write.csv(plot_data[[plot_id]], file, row.names = FALSE)
     }
  )
}

If I generate multiple plot, all the downloadButton & downloadLink are linked to the last generated plot because function(file){...} is interpreted on user click, then plot_id is set to the last value set by the for loop.

I've also tried to generate an anonymous function with as.function(), to set a default parameter function(file,local_plot_id=plot_id), or with a wrapper function.


Solution

  • I finally found an answer to my problem using the local function

    local evaluates an expression in a local environment. It is equivalent to evalq except that its default argument creates a new, empty environment. This is useful to create anonymous recursive functions and as a kind of limited namespace feature since variables defined in the environment are not visible from the outside.

    Here's a sample code for local usage in my problem

    for (plot_id in plot_ids) {
      output[[plot_id]] <- local({
        local_plot_id <- plot_id
        downloadHandler(
          filename = function() {
            paste0("custom_export_", local_plot_id, ".csv")
          },
          content = function(file){
              write.csv(plot_data[[local_plot_id]], file, row.names = FALSE)
          }
        )
      })
    }