Skip to content

Instantly share code, notes, and snippets.

@dkulp2
Created December 27, 2018 05:56
Show Gist options
  • Save dkulp2/b77ec1dc0031f2838f9dae08436efd35 to your computer and use it in GitHub Desktop.
Save dkulp2/b77ec1dc0031f2838f9dae08436efd35 to your computer and use it in GitHub Desktop.
Example shiny dynamic download
# Author: dkulp2
#
# Shiny example for dynamically downloading a file related to a specific row.
#
# A DT datatable is created with options that adds a 'download-control' class to a column with a download icon
# and a callback to set up the click event on the 'download-control' cells.
# The javascript click event determines which page of the datatable is being displayed and which row the click event occurred.
# This approach fails if rows are reordered or the table is filtered, so both are disabled. An alternative is to place the
# row ID in a "data-" attribute, which would allow reordering and filtering.
# It then sends a Shiny event, 'download-row', with the index of the row in the table.
# The shiny event then creates the data file based on the row number and sends the raw data to javascript via the 'download-data' message.
# In this example I create a data frame with the row index in it and send it as an RDS.
# The javascript message handler for 'download-data' decodes the raw data and triggers the client to download the data as a file.
# This approach will not perform well for large data due to latency and memory. An alternative is to store the file
# dynamically in the www/ folder and send the URL to the client.
library(shiny)
library(DT)
JS.row.dl <- htmlwidgets::JS("
table.on('click', 'td.download-control',
function() {
start=table.page.info().start
i=$(this).closest('tr')[0].rowIndex;
Shiny.setInputValue('download_row', {row:start+i}, {priority:'event'})
});")
ui <- fluidPage(
div(style="display: none", icon("")), # hack to trigger inclusion of icon library
dataTableOutput("myTable"),
includeScript("download.js")
)
server <- function(input, output, session) {
output$myTable <- renderDataTable(DT::datatable(data.frame(' '=as.character(icon('download')), 'alphabet'=LETTERS),
escape=FALSE,
selection='single',
options=list(ordering=FALSE,
dom="ltip", # disable filtering
columnDefs=list(list(className='download-control',targets=1))),
callback=JS.row.dl))
observeEvent(input$download_row, {
row.i <- input$download_row$row
message("clicked row: ",row.i)
rc <- rawConnection(raw(0), "r+")
saveRDS(data.frame(x=row.i), rc)
data <- rawConnectionValue(rc)
close(rc)
session$sendCustomMessage("download-data",
list(row=row.i, data=data, filename='df.RDS'))
}, ignoreInit=TRUE)
}
shinyApp(ui = ui, server = server)
// https://stackoverflow.com/a/20151856
function base64toBlob(base64Data, contentType) {
contentType = contentType || '';
var sliceSize = 1024;
var byteCharacters = atob(base64Data);
var bytesLength = byteCharacters.length;
var slicesCount = Math.ceil(bytesLength / sliceSize);
var byteArrays = new Array(slicesCount);
for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
var begin = sliceIndex * sliceSize;
var end = Math.min(begin + sliceSize, bytesLength);
var bytes = new Array(end - begin);
for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
bytes[i] = byteCharacters[offset].charCodeAt(0);
}
byteArrays[sliceIndex] = new Uint8Array(bytes);
}
return new Blob(byteArrays, { type: contentType });
}
function saveData(blob, fileName) // does the same as FileSaver.js - https://stackoverflow.com/a/33664602
{
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
}
Shiny.addCustomMessageHandler('download-data', function(args) {
console.log("Downloading table for row " + args.row);
saveData(base64toBlob(args.data), args.filename);
});
@csmatyi
Copy link

csmatyi commented Sep 27, 2022

wow such long code! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment