Search code examples
rshiny

R Shiny - How to have an action button that automatically downloads a csv file (Not download button?)


I have a shiny app where users enter data and then click an action button to submit that data into a database. I also have a download button that lets them download a copy of the data locally as a csv.

What I am trying to now achieve is have that data automatically download when the user clicks the action button. I have looked online and couldn't find much info this. Any help or thoughts is apprecaited.

I have a small app code below.

library(shiny)
library(gt)

#------------------------------#
# GLOBAL
#------------------------------#
gt_tbl <-
  gtcars %>%
  gt() 

#------------------------------#
# UI
#------------------------------#
ui <- fluidPage(
  downloadButton('downloadData', 'Download data'),
  actionButton('button', "ACTOIN BUTTON"),
  gt_output(outputId = "table")
)

#------------------------------#
# SERVER
#------------------------------#
server <- function(input,output,session) {
  
  
  #ACTION BUTTON 
  observeEvent(input$button, {
    withProgress(message = "You clicked this button.... Working...", value =0 , {
    Sys.sleep(3)
    
    #INSERT FUNCTION HERE LIKE WRITING TO DB
    incProgress(.25,message = "Writing to database.... Working...")
    Sys.sleep(3)
    
    #AUTOMATICALLY DOWNLOAD BUTTON NEXT?
    incProgress(.35, message = "Saving a copy of data locally.... Working...")
    #output$downloadData
    
    Sys.sleep(3)
    incProgress(1, message = "COMPLETE!")
    Sys.sleep(3)
    })
  })
  
  # TABLE
  output$table <- render_gt(
    expr = gt_tbl
    )
  
  # TRADITIONAL DOWNLOAD BUTTON 
  output$downloadData <- downloadHandler(
    filename = function() { 
      paste("dataset-gtcars-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      write.csv(gtcars, file)
    })

}

#------------------------------#
# RUN APP
#------------------------------#
shinyApp(ui=ui,server=server)



Solution

  • Combining the answer found here with your code, you can do this:

    library(shiny)
    library(gt)
    library(shinyjs)
    
    #------------------------------#
    # GLOBAL
    #------------------------------#
    gt_tbl <-
      gtcars %>%
      gt() 
    
    # function which checks the data; returns TRUE or FALSE
    checkData <- function(dat){
      TRUE
    }
    
    # function which transforms the data; returns NULL if check not TRUE
    processData <- function(dat){
      if(checkData(dat)){
        # do something with dat
        names(dat) <- toupper(names(dat)) # for our example
        return(dat)
      }else{
        return(NULL)
      }
    }
    
    
    #------------------------------#
    # UI
    #------------------------------#
    ui <- fluidPage(
      useShinyjs(),
      conditionalPanel(
        "false", # always hide the download button
        downloadButton("downloadData")
      ),
      actionButton("button", "Download"),
      gt_output(outputId = "table")
    )
    
    #------------------------------#
    # SERVER
    #------------------------------#
    server <- function(input,output,session) {
      
      finalData <- reactiveVal() # to store the processed data
      
      #ACTION BUTTON 
      observeEvent(input$button, {
        withProgress(message = "You clicked this button.... Working...", value =0 , {
          Sys.sleep(3)
          
          #INSERT FUNCTION HERE LIKE WRITING TO DB
          incProgress(.25,message = "Writing to database.... Working...")
          Sys.sleep(3)
          
          # PROCESS DATA
          if(!is.null(df <- processData(gtcars))){
            finalData(df)
            runjs("$('#downloadData')[0].click();") # DOWNLOAD BUTTON
          }else{
            # something which throws an alert message "invalid data" 
            # (eg with shinyBS::createAlert or shinyWidgets::sendSweetAlert)
          }
          
          Sys.sleep(3)
          incProgress(1, message = "COMPLETE!")
          Sys.sleep(3)
        })
      })
      
      # TABLE
      output$table <- render_gt(
        expr = gt_tbl
      )
      
      # DOWNLOAD BUTTON 
      output$downloadData <- downloadHandler(
        filename = function() { 
          paste("dataset-gtcars-", Sys.Date(), ".csv", sep="")
        },
        content = function(file) {
          write.csv(finalData(), file)
        })
      
    }
    
    #------------------------------#
    # RUN APP
    #------------------------------#
    shinyApp(ui=ui,server=server)
    
    

    The app includes an action button that simulates a process of writing data to a database and then downloading the processed data as a CSV file. The processData function takes the input data and transforms it (in this case, it changes the column names to all uppercase; I kept that the same from the other example answer). The checkData function always returns TRUE, but you can customize it to check the validity of your data.

    When the action button is clicked, a progress bar appears with a message indicating that the app is working. After a few seconds of delay, the data is processed, and the download button is automatically clicked (using JavaScript) to download the .csv file. If the data is invalid, it could throw an alert message using shinyBS::createAlert, shinyWidgets::sendSweetAlert functions etc (the code doesn't include this functionality, I just wrote in the same comment from the other answer).

    The solution is mainly to hide the download button by default using a conditionalPanel with "false" as the condition. This ensures that the download button only appears after the user clicks the action button and the data is processed successfully.