1

I am working on a shiny app where the user will upload an excel file, the data will be manipulated, and then a new excel file with this data is exported for the user to examine. I am having issues with the downloadHandler function. I used to create an entirely new excel file every time based on the uploaded data like this:

output$export <- downloadHandler(
filename = "answers.xlsx",
content = function(file){
  write.xlsx(exportdata(), file)
     })
  })

This works fine.

Now I would like to edit an excel file that I will include when I publish the app and allow the user to download this edited version like this:

output$export <- downloadHandler(
filename = "answers.xlsx",
content = function(file){
  wb <- loadWorkbook("6rep-charts.xlsx")
  writeData(wb, sheet = "Species Match Results", correlInput())
  writeData(wb, sheet = "BS1 Data", bs1Input())
  writeData(wb, sheet = "BS2 Data", bs2Input())
  saveWorkbook(wb, file)
})

However, this results in the error Warning: Error in write_file: Expecting a single string value: [type=character; extent=0]. [No stack trace available]. I am not sure what is going wrong as when I run the content section outside of the shiny app, it works just fine. The problem seems to be in the saveWorkbook command.

The reason I would like to edit an existing excel file rather than create a new one is that the template file I'm including in the app has charts already made that should change when the new data is written into the file. The users would like to be able to edit these charts themselves, rather than just see a picture of a graph. If anyone has a simpler way to accomplish this, that would be great! Thank you in advance for your help!

Reproducible example using this excel file:

    library(shiny); library(readxl); library(xlsx)
    ui <- shinyUI(fluidPage(

    titlePanel("Old Faithful Geyser Data"),
    fluidRow(
       column(3,
          downloadButton(outputId = "export",
               label = "Export Results to Excel")
  ),

       column(6,
         dataTableOutput("data")
  ))))

    server <- function(input, output) {
       adata <- faithful[1:20,]   
       bdata <- faithful[21:50,]   
       cdata <- faithful[51:200,]

   read_excel_allsheets <- function(filename, tibble = FALSE) {
      sheets <- readxl::excel_sheets(filename)
      x <- lapply(sheets, function(Y) {readxl::read_excel(filename, sheet = Y)})
      if(!tibble) x <- lapply(x, as.data.frame)
      names(x) <- sheets
      x
   }

   output$data <- renderDataTable({
       adata   })

   output$export <- downloadHandler(
       filename = "answers.xlsx",
       content = function(file){
          wb <- loadWorkbook("./Data/template.xlsx")
          writeData(wb, sheet = "Alpha", adata)
          writeData(wb, sheet = "Beta", bdata)
          writeData(wb, sheet = "Gamma", cdata)
          saveWorkbook(wb, file="./Data/temp.xlsx", overwrite = T)
          print("done")
          Fin_WB<- read_excel_allsheets("./Data/temp.xlsx")
          write.xlsx(Fin_WB, file)
        }   ) }

   shinyApp(ui = ui, server = server)
  • See https://stackoverflow.com/questions/34643888/how-do-i-append-data-from-a-data-frame-in-r-to-an-excel-sheet-that-already-exist – Chabo Oct 16 '18 at 20:38
  • Also https://stackoverflow.com/questions/46938075/unable-to-append-r-data-frame-into-existing-excel-without-overwriting?rq=1 – Chabo Oct 16 '18 at 20:40
  • Maybe append the file outside of the content function, and then just write the file inside the download handler? – Chabo Oct 16 '18 at 20:46
  • I would also ensure the application is able to access the workbook, as sometimes when publishing data along with an app, a separate data directory needs to be specified and used in the data path. See https://stackoverflow.com/questions/27793616/how-to-deploy-shiny-app-that-uses-local-data – Chabo Oct 16 '18 at 20:52
  • Thanks for the quick response @Chabo, I've added an example that might explain what's going on. I think the problem has to do with the `saveWorkbook` function, but I'm not 100% sure. – Zoe Bleicher Oct 16 '18 at 21:03
  • What package are the `Workbook` functions under? – Chabo Oct 16 '18 at 21:39

2 Answers2

1

Simpler solution (I am unable to leave comments due to too low reputation):

output$export <- downloadHandler(
filename = "answers.xlsx",
content = function(file){
  wb <- loadWorkbook("./Data/template.xlsx")
  writeData(wb, sheet = "Alpha", adata)
  writeData(wb, sheet = "Beta", bdata)
  writeData(wb, sheet = "Gamma", cdata)
  saveWorkbook(wb, file="./Data/temp.xlsx", overwrite = T)
  file.copy(from = "./Data/temp.xlsx", to = file)
}) 

No need for the function read_excel_allsheets.

0

The saveWorkbook function does not like to operate as a write function for the content function. Confusing yes, but we can still use it within the content function, just not as the final step. We have to use a classic write function such as write.xlsx to satisfy the content function which is expecting the file and file path to write to.

To get around this we create a temp file in the Data folder (this folder will be in the dir with server and ui, and will be published). The program reads in the template file (see special function below), modifies it, and then writes/overwrites the temp file. This temp file is then read in and assigned by read.xlsx and then written to by write.xlsx. This satisfies the content function and allows us to use a template for export.

read_excel_allsheets <- function(filename, tibble = FALSE) {
    sheets <- readxl::excel_sheets(filename)
    x <- lapply(sheets, function(X) readxl::read_excel(filename, sheet = X))
    if(!tibble) x <- lapply(x, as.data.frame)
    names(x) <- sheets
    x
  }

output$export <- downloadHandler(
    filename = "answers.xlsx",
    content = function(file){
      wb <- loadWorkbook("./Data/template.xlsx")
      writeData(wb, sheet = "Alpha", adata)
      writeData(wb, sheet = "Beta", bdata)
      writeData(wb, sheet = "Gamma", cdata)
      saveWorkbook(wb, file="./Data/temp.xlsx", overwrite = T)
      print("done")
      Fin_WB<- read_excel_allsheets("./Data/temp.xlsx")
      write.xlsx(Fin_WB, file)
    }) 
  }

In order to read in all the sheets, we cannot simply use read.xlsx as it wont look at all the sheets. Using a function created here by Jeromy Anglim, we can read in all the sheets from excel. This is the read_excel_allsheets function.

Chabo
  • 2,842
  • 3
  • 17
  • 32
  • Thank you so much for your help! I have tried what you suggested - creating a data folder containing the template.xlsx file and a new temp.xlsx file, adding the `read_excel_allsheets` function, and adjusting the `content` function - but still get the `Error in write_file: Expecting a single string value: [type=character; extent=0].` error. Any thoughts on why that is? I've updated the question to include the whole current code. – Zoe Bleicher Oct 18 '18 at 17:25
  • @Zoe Bleicher Make sure you have installed and are using `library(openxlsx)` – Chabo Oct 18 '18 at 17:49
  • After inserting `library(openxlsx)` to your code the program worked as expected. Note that the download handler works better when used in the browser (correct filename & type), so test it there. If the program still does not work ensure the Data folder is in the project directory or whatever directory the server and UI are in. You can also try to open and re-save the excel file, sometimes excel is strange. – Chabo Oct 18 '18 at 17:57
  • Opening and re-saving the excel files did the trick. Thanks!! – Zoe Bleicher Oct 18 '18 at 18:58