0

Context: I have an app transforming data according to user's choices. It creates a few tables and plots in the process.

Objective: to save some objects created in the process into one new folder with one click on a button.

Previous researches: the code below saves objects using downloadHandler() and some functions as presented here. It does not seems to allow multiple objects to be passed into downloadHandler(). I am aware it is possible to stack these objects in a list and then save it but if possible I would like to avoid doing it and instead get multiple files (like .txt or .png, ...)

Here is a reproductible example with very little data using datasets included in R (mtcars and iris).

library(shiny)

ui <- fluidPage(
    downloadButton("save", "Save") # one click on this button to save df1 AND df2 tables in a new folder
)

server <- function(input, output) {
    # my real app does multiple changes on datasets based on user choices
    df1 = mtcars[1:10,]
    df2 = iris[1:10,]

    # Now I want to save df1 and df2 objects with 1 click on the "Save" button

    output$save = downloadHandler(
        filename = function(){ paste("example", ".txt", sep = " ") },
        content = function(file) { write.table(df1, file) }
    )
}

# Run the application 
shinyApp(ui = ui, server = server)

Many thanks for your help and suggestions!

Paul
  • 2,850
  • 1
  • 12
  • 37

1 Answers1

1

As noted in the comments of the linked post, it's not typically a good idea to change the working directory (and unnecessary in this case). While inconsequential with a small number of files, the paste0 call to create the path doesn't need to be in the for loop as it is vectorized. This also eliminates the need to dynamically grow the fs vector (also generally a bad practice). Lastly, my zip utility wasn't on my path which caused the utils::zip to fail (you can specify the path in the function call, otherwise it checks for the environment variable R_ZIPCMD and defaults to 'zip' assuming it to be on the path).

I generally agree with the accepted answer, but here's an alternative solution using the zip::zipr function instead (also walk instead of the for loop)

library(shiny)
library(purrr)
library(zip)

ui <- fluidPage(
  downloadButton("save", "Save") # one click on this button to save df1 AND df2 tables in a new folder
)

server <- function(input, output) {
  # my real app does multiple changes on datasets based on user choices
  df1 <- mtcars[1:10,]
  df2 <- iris[1:10,]

  # need to names these as user won't be able to specify
  fileNames <- paste0("sample_", 1:2, ".txt")

  output$save = downloadHandler(
    filename = function(){ paste0("example", ".zip") },
    content = function(file) { 

      newTmpDir <- tempfile()
      if(dir.create(newTmpDir)){

        # write data files
        walk2(list(df1, df2), fileNames, 
                        ~write.table(.x, file.path(newTmpDir, .y))
        )

        # create archive file
        zipr(file, files = list.files(newTmpDir, full.names = TRUE))

      }
    },
    contentType = "application/zip"
  )
}
Marcus
  • 3,478
  • 1
  • 7
  • 16
  • Many thanks for your clear answer. I have tried not to use the `zip` package unsuccessfully. I trust your experience and implement it as you propose. – Paul Apr 06 '20 at 09:10