I have a shiny app that uploads pdfs to do some checks on them and write a report to a table for the user to see. One of the requirements is to create a link to the document that downloads the initial uploaded pdf. Is there a way to access the temp directory files for download and put that download link in a DT datatable? I've tried coping files to www and they can be accessed that way but when the session ends the files are not deleted.
library(shiny)
library(DT)
ui <- fluidPage(
fileInput('pdfFile',
'Upload PDF',
multiple = TRUE,
accept = c('.pdf')),
DTOutput('Table')
)
server <- function(input, output) {
output$Table <- renderDT({
pdfTable <- cbind(input$pdfFile,
pdflink = sprintf('%s',
input$pdfFile$datapath,
input$pdfFile$name,
input$pdfFile$name),
stringsAsFactors = FALSE)
datatable(pdfTable,escape = FALSE)
})
}
shinyApp(ui = ui, server = server)
You can use session$onSessionEnded to execute some code after the client has disconnected (I confess I never tried):
server <- function(input, output, session) {
session$onSessionEnded(function(){
file.remove(......)
})
output$Table <- renderDT({
pdfTable <- cbind(input$pdfFile,
pdflink = sprintf('%s',
input$pdfFile$datapath,
input$pdfFile$name,
input$pdfFile$name),
stringsAsFactors = FALSE)
datatable(pdfTable, escape = FALSE)
})
}
I wasn't able to get the downloadButton to appear in the table, but the otherwise I believe the following meets your requirements. The basic idea is to copy the uploaded file to a new tempfile whose location gets saved in a reactiveVal until needed.
library(shiny)
library(tidyverse)
library(DT)
ui <- fluidPage(
fileInput('pdfFile',
'Upload PDF',
multiple = TRUE,
accept = c('.pdf')),
downloadButton("download_button", "Download Selected File"),
DTOutput('Table')
)
server <- function(input, output) {
output$Table <- renderDT({
uploaded_df() %>%
select(-temp) %>%
datatable(selection = "single")
})
uploaded_df <- reactiveVal(tibble(name = character(), temp = character()))
observeEvent(input$pdfFile,{
temp_file_location <- tempfile(fileext = ".pdf")
file.copy(input$pdfFile$datapath, temp_file_location)
tibble(name = input$pdfFile$name,
temp = temp_file_location) %>%
bind_rows(uploaded_df(), .) %>%
uploaded_df()
})
output$download_button <- downloadHandler(
filename <- function() {
req(input$Table_rows_selected)
uploaded_df()$name[[input$Table_rows_selected]]
},
content <- function(file) {
file.copy(uploaded_df()$temp[[input$Table_rows_selected]], file)
}
)
}
shinyApp(ui = ui, server = server)
Related
I would like to have a shiny app that, when run for the first time, displays a dataframe defined as a template, and then the user can upload a new one (in csv only) that replaces the current one. Therefore, in case the user imports a file of the wrong type, it produces a message instead. Here is my code, which results in an error, and I don't know why it doesn't work
library(shiny)
library(DT)
library(dplyr)
library(shiny)
ui <- fluidPage(
fileInput("upload", NULL, accept = c(".csv")),
tableOutput("head")
)
server <- function(input, output, session) {
rv <- reactiveValues(
dataframe = NULL
)
observe({
if(is.null(rv$dataframe)){
dataFrameFile <- reactive({
df <- data.frame(
x = seq(1:12),
y = rnorm(12))
rv$dataframe <- datatable(df)
return(rv$dataframe)
})
} else {
dataFrameFile <- reactive({
req(input$upload)
ext <- tools::file_ext(input$upload$name)
rv$dataframe <- switch(ext,
csv = read.csv(input$upload$datapath),
validate(" Please upload a .csv file")
)
})
}
})
output$head <- renderDT({
datatable(dataFrameFile())
})
}
shinyApp(ui, server)
A few corrections/simplifications:
Used DTOutput instead of tableOutput to correspond to renderDT
directly initialized rv
put the validate in the renderDT
library(shiny)
library(DT)
library(dplyr)
library(shiny)
ui <- fluidPage(
fileInput("upload", NULL, accept = c(".csv")),
DTOutput("head")
)
server <- function(input, output, session) {
rv <- reactiveValues(
dataframe = data.frame(
x = seq(1:12),
y = rnorm(12))
)
observe({
req(input$upload)
ext <- tools::file_ext(input$upload$name)
rv$dataframe <- switch(ext,
csv = read.csv(input$upload$datapath),
NULL)
})
output$head <- renderDT({
validate(need(!is.null(rv$dataframe)," Please upload a .csv file"))
rv$dataframe
})
}
shinyApp(ui, server)
I modified the example here to include a save button as well. I want the user to be able to reset to the initial table after uploading a new file by adding a reset button (similar to the save button), but I wonder if it's possible to do so.
EDIT:
I want the button to be a part of the DT table and be placed next to the save.
library(shiny)
library(DT)
library(dplyr)
ui <- fluidPage(
fileInput("upload", NULL, accept = c(".csv")),
DTOutput("head")
)
server <- function(input, output, session) {
rv <- reactiveValues(
dataframe = data.frame(
x = seq(1:12),
y = LETTERS[1:12])
)
observe({
req(input$upload)
ext <- tools::file_ext(input$upload$name)
rv$dataframe <- switch(ext,
csv = read.csv(input$upload$datapath),
NULL)
})
output$head <- renderDT({
validate(need(!is.null(rv$dataframe)," Please upload a .csv file"))
datatable(rv$dataframe, extensions = 'Buttons',
options = list(
dom = 'Bfrtip',
buttons = list(list( extend = 'csv',
filename = '//public/comments/comments.csv',
text = 'Save'))))
})
}
shinyApp(ui, server)
Here is a snapshot of the current version:
There could be multiple ways to handle this. Here is one of it -
Used a fixed dataframe mtcars[1:6, 1:6] as the default dataframe instead of one which generates random numbers which is difficult to compare imo.
Added an actionButton for Reset feature.
Created another reactive variable called dataframe_copy which always holds the default dataframe.
library(shiny)
library(DT)
library(dplyr)
ui <- fluidPage(
fileInput("upload", NULL, accept = c(".csv")),
DTOutput("head"),
actionButton('reset', 'Reset')
)
server <- function(input, output, session) {
rv <- reactiveValues(
dataframe = mtcars[1:6, 1:6],
dataframe_copy = mtcars[1:6, 1:6]
)
observe({
req(input$upload)
ext <- tools::file_ext(input$upload$name)
rv$dataframe <- switch(ext,
csv = read.csv(input$upload$datapath),
NULL)
})
observeEvent(input$reset, {
rv$dataframe <- rv$dataframe_copy
})
output$head <- renderDT({
validate(need(!is.null(rv$dataframe)," Please upload a .csv file"))
datatable(rv$dataframe, extensions = 'Buttons',
options = list(
dom = 'Bfrtip',
buttons = list(list( extend = 'csv',
filename = '//public/comments/comments.csv',
text = 'Save'))))
})
}
shinyApp(ui, server)
In the following app, I would like to add a global button, to save the tables in the 2 panels at the same time.
Ideally, they should be saved to an xlsx file, in tabs named after the corresponding tabs.
Please note that the tabs were created using a module.
Many thanks!!
library(shiny)
library(DT)
modDtUi <- function(id){ # UI module
ns = NS(id)
DT::dataTableOutput(ns('x1'))
}
modDt <- function(input, output, session, data, globalSession){ # Server module
x <- data
output$x1 <- DT::renderDataTable(x, selection = 'none', editable = TRUE)
proxy <- dataTableProxy('x1', session = globalSession)
}
ui <- fluidPage(
mainPanel(
tabsetPanel(
tabPanel("Table1", modDtUi("editable")),
tabPanel("Table2", modDtUi("editable2"))
)
)
)
server <- function(input, output, session) {
callModule(modDt,"editable", data = head(iris,10), globalSession = session)
callModule(modDt,"editable2", data = tail(iris,5), globalSession = session)
}
shinyApp(ui = ui, server = server)
I believe this demo works.
I used reactiveValues v$data to store the data inside the module. The module will return v$data so it can be retrieved when you want to save the data in the shiny server.
I also added an observeEvent to detect changes in the data, and update the data table with replaceData.
The excel file is created using the writexl library, but you could substitute with others of course.
Let me know if this works for you. I imagine there are some elements of this answer that can be improved upon - and if we can identify them, would like to edit further.
library(shiny)
library(DT)
library(writexl)
modDtUi <- function(id){ # UI module
ns = NS(id)
DT::dataTableOutput(ns(id))
}
modDt <- function(input, output, session, data, id, globalSession){ # Server module
v <- reactiveValues(data = data)
output[[id]] <- DT::renderDataTable(v$data, selection = 'none', editable = TRUE)
proxy <- dataTableProxy(id, session = globalSession)
id_input = paste(id, "cell_edit", sep = "_")
# Could add observeEvent here to detect edit event
observeEvent(input[[id_input]], {
info = input[[id_input]]
if (!is.null(info)) {
v$data[info$row, info$col] <<- DT::coerceValue(info$value, v$data[info$row, info$col])
}
replaceData(proxy, v$data, resetPaging = FALSE)
})
return(data = reactive({v$data}))
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
width = 2,
actionButton("btn", "Save Both")
),
mainPanel(
tabsetPanel(
tabPanel("Table1", modDtUi("editable1")),
tabPanel("Table2", modDtUi("editable2"))
)
)
)
)
server <- function(input, output, session) {
e1 <- callModule(modDt, "editable1", data = head(iris,10), id = "editable1", globalSession = session)
e2 <- callModule(modDt, "editable2", data = tail(iris,5), id = "editable2", globalSession = session)
observeEvent(input$btn, {
print("Saving...")
sheets <- list("e1" = e1(), "e2" = e2())
write_xlsx(sheets, "test.xlsx")
})
}
shinyApp(ui = ui, server = server)
I wrote a Shiny app that loads several user-defined csv files as fileInput. The app is designed to plot data from a running measurement and new datapoints are written to the input files about every five minutes. I want to be able to reload all inputs by clicking on an actionButton.
I tried to define the function reading the .csv as eventReactive:
library(shiny)
ui <- fluidPage(
actionButton(inputId = "update", label = "Reload input files"),
fileInput(inputId = "file", label = "Choose file"),
textOutput("test")
)
server <- function(input, output) {
data <- eventReactive(input$update, {
mydata <- read.delim(input$file$datapath)
return(nrow(mydata))
})
output$test <- renderText(print(data()))
}
shinyApp(ui = ui, server = server)
When I choose an input file and click the action button, the output is correctly rendered. If I now open the csv file, add additional rows and click the action button again, the output is not updated.
Based on this answer I was able to create a workaround for you problem.
As I pointed out in my comment above, the reason why it is not possible to update fileInput with an action button is that, apparently, fileInput creates a temporary file in a temporary directory and the Input$file$datapth links to this temporary file. So you can reload the file with using the action button as often as you like, changes to the orignial file will not be reflected, since the link is pointing to the temporary file. I really don't know why inputFile works with temp files, but using the shinyFiles packages, you can build a workaround. You have one button which gets the real link to your file and load the data in and another button to reload the data. Pressing the load button will reload the original data and all changes to it will be reflected.
library(shiny)
library(shinyFiles)
ui <- fluidPage(
shinyFilesButton("GetFile", "Choose a file" ,
title = "Please select a file:", multiple = FALSE,
buttonType = "default", class = NULL),
actionButton(inputId = "reload", label = "Reload data"),
tableOutput("test")
)
server <- function(input,output,session){
volumes <- getVolumes()
v = reactiveValues(path = NULL)
observe({
shinyFileChoose(input, "GetFile", roots = volumes, session = session)
if (!is.null(input$GetFile)) {
file_selected <- parseFilePaths(volumes, input$GetFile)
v$path <- as.character(file_selected$datapath)
req(v$path)
v$data <- read.csv(v$path)
}
})
observeEvent(input$reload, {
req(v$path)
v$data <- read.csv(v$path)
})
output$test <- renderTable({
print(v$path)
if (is.null(v$data)) return()
v$data
})
}
shinyApp(ui = ui, server = server)
Update
It is also possible to combine this approach with reactiveFileReader, see example below:
library(shiny)
library(shinyFiles)
ui <- fluidPage(
shinyFilesButton("GetFile", "Choose a file" ,
title = "Please select a file:", multiple = FALSE,
buttonType = "default", class = NULL),
tableOutput("test")
)
server <- function(input,output,session){
volumes <- getVolumes()
v = reactiveValues(path = NULL)
observe({
shinyFileChoose(input, "GetFile", roots = volumes, session = session)
req(input$GetFile)
file_selected <- parseFilePaths(volumes, input$GetFile)
v$path <- as.character(file_selected$datapath)
req(v$path)
v$data <- reactiveFileReader(1000, session, filePath = v$path, readFun = read.csv, sep = ";")
})
output$test <- renderTable({
print(v$path)
req(v$data)
v$data()
})
}
shinyApp(ui = ui, server = server)
I am trying to download output from wordcloud2 on shiny.
My code is as below:
library(shiny)
library(htmlwidgets)
library(webshot)
ui <- shinyUI(fluidPage(mainPanel(
wordcloud2Output("wordcl"),
downloadButton(outputId = "savecloud"),
downloadButton(outputId = "savecloud2")
)))
server <- shinyServer(function(input, output, session) {
wordcl <- reactive ({
wordcloud2(demoFreq, color = "random-light", backgroundColor = "grey")
})
output$wordcl <- renderWordcloud2({ wordcl() })
##### SOLUTION 1 #########
output$savecloud <- downloadHandler(
filename = "word.png",
content = function(cloud) {
file.copy(wordcl(), cloud)
})
##### SOLUTION 2 ##########
output$savecloud2 <- downloadHandler(
saveWidget(wordcl(), file="temp.html", selfcontained = F),
webshot("temp.html", file = "word2.png",
cliprect = "viewport")
)
})
shinyApp(ui = ui, server = server)
I have tried two styles using downloadhandler as shown in the code but they return empty results.
Any insight on why they downloadhandler doesn't work or redirection on how best to effect the download function will be appreciated.
I managed to make my download work by using an example of download handler function posted on LeafletMaps here: Why is webshot not working with leaflets in R shiny?
My updated code is as below:
library(shiny)
library(htmlwidgets)
library(webshot)
library(wordcloud2)
#webshot::install_phantomjs()
ui <- shinyUI(fluidPage(mainPanel(
wordcloud2Output("wordcl"),
downloadButton(outputId = "savecloud")
)))
server <- shinyServer(function(input, output, session) {
wordcl <- reactive ({
wordcloud2(demoFreq, color = "random-light", backgroundColor = "grey")
})
output$wordcl <- renderWordcloud2({
wordcl()
})
output$savecloud <- downloadHandler(
filename = paste("wordcloud", '.png', sep=''),
content = function(file) {
owd <- setwd(tempdir())
on.exit(setwd(owd))
saveWidget(wordcl(), "temp.html", selfcontained = FALSE)
webshot("temp.html", delay =15, file = file, cliprect = "viewport")
})
})
shinyApp(ui = ui, server = server)
The solution given on the link seems to combine the solutions I was trying to implement in my original post.
The only issue is that it does not work when the app is deployed on shiny.io