Search code examples
rexcelshinydownloadreactable

Download data into excel from r shiny table created with reactable


I have a shiny dashboard where the tables are created with the reactable package. I have simple and nested tables and as far as I can see, there is only a download option for csv:

library(htmltools)
library(fontawesome)

data <- MASS::Cars93[1:15, c("Manufacturer", "Model", "Type", "Price")]

htmltools::browsable(
  tagList(
    tags$button(
      tagList(fontawesome::fa("download"), "Download as CSV"),
      onclick = "Reactable.downloadDataCSV('cars-download-table', 'cars.csv')"
    ),

    reactable(
      data,
      searchable = TRUE,
      defaultPageSize = 5,
      elementId = "cars-download-table"
    )
  )
)

I want to create one Excel download file with the following attributes:

  • the tables to download are selected via a checkboxGroupInput
  • one Excel sheet per selected item
  • the name of the sheet corresponds to selected item
  • if there is more than one table in the selected item, all those tables should be in one sheet (divided by some empty rows)
  • some captions (read from another file) should be inserted above the tables

The problem is, that I want to use the data shown in the reactable (e.g. the selected columns), therefore I can not use the raw data. Is there some kind of package I can use? So far, I only have a slow solution where I put the reactable into an additional variable before I render the table and then I read the data from this variable and use the package openxlsx to write the Excel.


Solution

  • Here is a clue. You can get the current state of the table with Reactable.getState, and the current display is in the field sortedData. This is demonstrated by the app below.

    library(shiny)
    library(reactable)
    library(jsonlite)
    
    registerInputHandler(
      "xx",
      function(data, ...){
        fromJSON(toJSON(data))
      },
      force = TRUE
    )
    
    ui <- fluidPage(
      fluidRow(
        column(
          7,
          tags$button(
            "Get data",
            onclick = '
              var state = Reactable.getState("cars");
              Shiny.setInputValue("dat:xx", state.sortedData);
            '
          ),
          reactableOutput("cars")
        ),
        column(
          5,
          verbatimTextOutput("data")
        )
      )
    )
    
    server <- function(input, output){
      output$cars <- renderReactable({
        reactable(MASS::Cars93[, 1:5], filterable = TRUE)
      })
    
      output$data <- renderPrint({
        input$dat
      })
    }
    
    shinyApp(ui, server)
    

    enter image description here


    EDIT

    Here is an example of downloading the current display:

    library(shiny)
    library(shinyjs)
    library(reactable)
    library(jsonlite)
    
    registerInputHandler(
      "xx",
      function(data, ...){
        fromJSON(toJSON(data))
      },
      force = TRUE
    )
    
    ui <- fluidPage(
      useShinyjs(),
      br(),
      conditionalPanel(
        "false", # always hide the download button, because we will trigger it 
        downloadButton("downloadData") # programmatically with shinyjs
      ),
      actionButton(
        "dwl", "Download", class = "btn-primary",
        onclick = paste0(
          'var state = Reactable.getState("cars");',
          'Shiny.setInputValue("dat:xx", state.sortedData);'
        )
      ),
      br(),
      reactableOutput("cars")
    )
    
    server <- function(input, output, session){
      
      output$cars <- renderReactable({
        reactable(MASS::Cars93[, 1:5], filterable = TRUE)
      })
      
      observeEvent(input$dat, {
        runjs("$('#downloadData')[0].click();")
      })
      
      output$downloadData <- downloadHandler(
        filename = function() {
          paste0("data-", Sys.Date(), ".xlsx")
        },
        content = function(file) {
          openxlsx::write.xlsx(input$dat, file)
        }
      )
    }
    
    shinyApp(ui, server)