downloadButton() dissapears when the df is reactive

I am attempting to use a custom download button with a DT datatable. I tried using the add on buttons feature but these do not allow for download of the entire dataframe.

I copied the methodology by stla on this Github issues page (right at the bottom) Downloading all the data using Buttons extension · Issue #267 · rstudio/DT · GitHub.

Reprex:


library(tidyverse)
library(shiny)
library(nycflights13)
library(lubridate)
library(DT)

# Define UI for application that draws a histogram
ui <- fluidPage(
   
   # Application title
   titlePanel("Example Shiny App"),
   
   # Sidebar with a slider input for number of bins 
   sidebarLayout(
      sidebarPanel(
        selectInput(inputId = "group_dims", 
                    label =  "group_dims", 
                    choices = c("carrier", "origin", "dest", "tailnum"),
                    selected = c("carrier"),
                    multiple = T) 
      ),
      
      # DT table
      mainPanel(
        downloadButton("download1","Download DF"),
         DT::dataTableOutput("eg_table")
      )
   )
)

# Define server logic required to draw a histogram
server <- function(input, output) {
  
  # initial preprocessing
  my_flights <- flights %>% 
    filter(month == 11 & day >= 14) %>% # just data for 2 weeks
    mutate(date = ymd(paste(year, month, day, sep = "-"))) %>% 
    select(date, carrier, origin, dest, tailnum, distance) %>% 
    mutate(date = ordered(format(date, "%d-%b"), levels = format(sort(unique(date)), "%d-%b")))
  
  
  # recative preprocessing
  my_flights_react <- reactive({
    dims <- input$group_dims
    my_flights %>%
      group_by_at(vars(date, dims)) %>%
      summarise(distance = sum(distance)) %>%
      pivot_wider(names_from = date, values_from = distance) %>%
      replace(is.na(.), 0) %>% 
      ungroup() %>% 
      add_column(Total = rowSums(select(., -dims), na.rm = T), .after = length(dims)) %>% 
      arrange(desc(Total))
    })

  
   output$eg_table <- DT::renderDT({my_flights_react()},
                                   callback = JS("$('div.dwnld').append($('#download1'));"),
                                   filter = 'top', 
                                   extensions = 'Buttons',
                                   options = list(dom = 'B<"dwnld">frtip',
                                                  autoWidth = T,
                                                  scrollX=T,
                                                  buttons = list("copy"),
                                                  columnDefs = list(list(width = '100px',targets = 1:length(input$group_dims)))
                                                  )
                                   )
   output$download1 <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      write.csv(my_flights_react(), file)
    }
  )
   
}

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

If you run that Shiny app, when it initially loads a download button appears as expected. However, if you make a election with the input selector, the download button disappears.

How can I adjust this app so that the download option remains after a selection is made by the user?


Hey @dougfir,

Not solving exactly your problem, but datatable has a default csv button to download the data on the table:

https://datatables.net/reference/button/csv

Just need to add it to the buttons list in the DT (with copy) and you are good to go. You wont need the Shiny button at all...

Hi @joseluizferreira thanks for the suggestion. This is actually what I tried initially:

I tried using the add on buttons feature but these do not allow for download of the entire dataframe

If you add all the available dimensions to the example app and use the download or copy buttons, only ~50 rows are copied or downloaded. This is why I wanted to write my own download button.

I realize that maybe this is more a BI tool problem than R Shiny problem. Any other suggestions welcome. I would like to stay in R because I like not having to switch contexts while building the analysis I'm working on.

If you add all the available dimensions to the example app and use the download or copy buttons, only ~50 rows are copied or downloaded. This is why I wanted to write my own download button.

Makes sense. By default DT renders server-side, so only the data in the current page is available for download. You can set server = FALSE to get all the data, but in your case I bet it will slow down or freeze the app.

Is it just a matter of style for you to want the two buttons together?

@joseluizferreira Yes, I should have clarified that I did try setting server = FALSE on my real data and it slows down loading of the app tab a lot, so I wanted to find an alternative solution.

Is there a conventional wisdom approach to this in R? I have a reactive dataframe with ~12.5K rows and ~35 columns. I need to find a way, ideally within the universe of R, to allow the user to make filter selections and copy or download the data. Any suggestions welcome?

Is it just a matter of style for you to want the two buttons together
I do not understand?!

The problem you are having is because you are appending the button to the datatable and it disappears for some unknown reason. If you don't append it (by just removing the callback), then the button will stay there (not in the dt, not align with the "copy" button, not of the same size as it, etc...). That's why I asked if it is an aesthetic issue that makes you dislike the default solution (an independent button).

Personally I would get rid of the copy button inside the datatable and keep the download button somewhere on the top right corner (outside the dt div).

Ah I see what you are asking me now. I have no preference for aesthetic. Right now I 100% care about functionality! I only used the callback because I copied the solution from the Github page I linked to at the top and that is what they did, I do not fully understand it's purpose. Should I just try deleting it? I just did! Everything seems to work now! But I don't understand why. Thank you for helping me.

The callback is essentially removing the button from its original position and placing it on the div that contain the copy button (it is a bit of a hack, but clever). It should work without problems if the data wasn't reactive...

For a reactive data, I suspect that the problem is that the download button is rendered once when initialized, but when the datatable updates because of the input, it flushes the button with it. Since the button does not exist anymore on its original place, it disappears.

Maybe someone else has a better solution for the problem above... for now, I am glad that it helped you!

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.