2

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.

Donald Seinen
  • 4,179
  • 5
  • 15
  • 40

1 Answers1

2

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)
Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225
  • This is a great solution. I have just one problem left: I have nested tables and `Reactable.getState` only returns data if the row was extended (which it is not by default). Is there something to change this behavior? Here is an example for a nested table: `data <- as.data.frame(unique(MASS::Cars93[, 1])) reactable(data, details = function(index){ part_data <- MASS::Cars93[MASS::Cars93[, 1] == data[index, 1], 2:5] htmltools::div(style = "padding: 1rem", reactable(part_data, outlined = TRUE, elementId = paste0('cars', index)))}, filterable = TRUE)` – TheSoundOfSnowCrunching Aug 11 '22 at 06:12
  • 1
    Together with ```Reactable.toggleAllRowsExpanded("cars", true); Reactable.toggleAllRowsExpanded("cars", false);```for nested tables I can get all the data. – TheSoundOfSnowCrunching Sep 12 '22 at 05:20