I am following the simple file upload example from shiny gallery, but with a slight modification. I am required to modify the csv files locally, and see the changes reflected on the UI. However, I believe that this is not possible unless we poll for any changes in the source.
Therefore, I simplify the problem, by allowing re-uploading of the file. But, this is also not happening in Shiny. Once a file "file1.csv" is uploaded, i cannot upload the same file again. I have to upload a different file "file2.csv" and then once again the original file "file1.csv".
This is just time consuming, and I am wondering if anyone has come across such an issue, and possibly found a solution for it.
Adding a jquery to clear the value of the fileInput does the trick.
...
fileInput("csvInput", "Upload CSV file", multiple = TRUE,
accept=c('text/csv',
'text/comma-separated-values,text/plain',
'.csv')),
tags$script('$( "#csvInput" ).on( "click", function() { this.value = null; });'),
...
Actually it is one of the shortcomings of Shiny's fileInput module: it stays still if the same file was selected again, and there is no force-upload option built-in.
To enforce re-uploading, the basic idea is:
After each time uploading, keep the uploaded data in a reactiveValues as storage.
Apply a new fileInput to replace original one
Show a successfully upload message under new fileInput for a better user experience.
In ui.R,using
uiOutput('fileImport')
In server.R:
# reactive storage
v <- reactiveValues()
# fileInput index
v$fileInputIndex <- 1
updateFileInput <- function (name = NULL){
# update output with a new fileInput module
output$fileImport <- renderUI({
index <- isolate(v$fileInputIndex)
result <- div()
result <- tagAppendChild(
result,
fileInput(paste0('file', index), 'Choose CSV File',accept=c('text/csv','text/comma-separated-values,text/plain','.csv'))
)
# show a message of successful uploading
if(!is.null(name)){
result <- tagAppendChild(
result,
div(name," upload complete")
)
}
result
})
}
dataInputRaw <- reactive({
# equals to `input$file1` when initialized
inFile <- input[[paste0('file', v$fileInputIndex)]]
# TICKY PART:
# 1. If initialized, `inFile` and `v$data` are both `NULL`
# 2. After each uploading, new `fileInput` is applied and
# we want to keep previous updated data.
# It also prevent recursive creation of new `fileInput`s.
if (is.null(inFile)){
return(v$data)
}
# load as data frame
v$data <- data.frame(read.csv(inFile$datapath))
# if file successfuly uploaded, increate the index
# then upload `fileInput` with successful message
if (!is.null(v$data)){
v$fileInputIndex <- v$fileInputIndex + 1
updateFileInput(name = inFile$name)
}
# return data
v$data
})
# init
updateFileInput()
I have tested this snippet and it works.
Related
I'm new to shiny and I would like your advice on a requirement that I have at my office. I apologize in advance for not providing more information or code at the moment.
I have currently coded a R script that does the following:
Import 7 excel files with read_excel:
File 1 will go to dataset 1
File 2 will go to dataset 2
File 3,4,5,6,7 will go to dataset 3 by using lapply
Does a whole lot of data cleaning, formatting, parsing and ordering
Merges everything together and creates a final excel and txt files with specific formatting
Im requiring a shiny web app that:
Provides 3 different upload boxes for the user. One for each type of file (1 / 2 / 3,4,5,6,7)
Internally saves the uploaded files so the code i already have can use them for its processing
Lets the user download the 2 output files made by my code to the computer
If possible, show a log window on the app so the user can know if something goes wrong with the code execution
Datasets visualization is not required
I might be asking a lot. I will appreciate if you just can give me some lights in how i can start working on this. I would like not to modify my current code, if possible (can i have shiny acquire the files, and call my code so it can process them?)
Here is a minimal example showing uploading files, processing them, and downloading them.
For simplicity I've used 3 inputs and a single output.
If you want to notify a user that something has happened, you can use showNotification()
library(shiny)
ui <- fluidPage(
#File Upload Boxes
fileInput("myfileinput_1", label = "Upload File 1", accept = ".csv"),
fileInput("myfileinput_2", label = "Upload File 2", accept = ".csv"),
fileInput("myfileinput_3", label = "Upload File 3", accept = ".csv"),
#Button
actionButton("mybutton", label = "Process Uploaded Files"),
#Table Showing Processed Data
tableOutput("mytable"),
#Download Buttons
downloadButton("myfiledownload", label = "Download Processed File")
)
server <- function(input, output, session) {
#A reactive dataframe to store our outputfile
reactives <- reactiveValues(
df_output = NULL
)
#Runs when button is pressed
observeEvent(input$mybutton, {
#Check that all 3 files are selected before loading
if(!is.null(input$myfileinput_1) & !is.null(input$myfileinput_2) & !is.null(input$myfileinput_3)) {
#Load input files
df_input_1 <- read.csv(input$myfileinput_1$datapath)
df_input_2 <- read.csv(input$myfileinput_2$datapath)
df_input_3 <- read.csv(input$myfileinput_3$datapath)
#Use input to create an output (we're just using a simple example)
reactives$df_output <- data.frame(
input = c("Input 1", "Input 2", "Input 3"),
rows = c(nrow(df_input_1), nrow(df_input_2), nrow(df_input_3))
)
showNotification("Files Successfully Processed", type = "message")
} else {
showNotification("Ensure all three files are selected before loading", type = "error")
}
})
#Table Output
output$mytable <- renderTable({
reactives$df_output
})
#Download handler
output$myfiledownload <- downloadHandler(
filename = "mydata.csv",
content = function(file) {write.csv(reactives$df_output, file, row.names = FALSE)}
)
}
shinyApp(ui, server)
I am trying to save the uploaded files (within my shiny app) to a remote dropBox. I have been trying to implement what I learned here: [https://shiny.rstudio.com/articles/persistent-data-storage.html ](Permanent Storage) but so far no luck.
What I need is to save and store these data files in a dropBox, and then (when needed) to show the datasets on the dashboard.
It looks a tricky task (at least for me, as I am new to shiny). Any support is very welcome!
Here it's a snippet of my code (on server):
#### To upload file and generate a Table ####
data <- reactive({
req(input$file)
ext <- tools::file_ext(input$file$name)
switch(ext,
csv = vroom::vroom(input$file$datapath, delim = ","),
tsv = vroom::vroom(input$file$datapath, delim = "\t"),
xlsx = read_excel(input$file$datapath, sheet = 1),
validate("Invalid file; Please upload a .csv, .tsv or excel file")
)
})
# When the Upload a File button is clicked, save the data
observeEvent(input$file, {
saveData(data())
})
# Show the table results on dashboard
output$table <- renderDataTable({
input$file
data()
# DropBox
loadData()
})
The functions loadData() and saveData() (they were taken from the link above) are defined before the ui and server pieces.
I am trying to accomplish something very similar to what this poster asked.
However, some of the values that the user is able to input are lists.
For example, the input value input$file1 has sub-values input$file1$name,
input$file1$size,
input$file1$type, and
input$file1$datatype.
Furthermore, I have two inputs, input$remove and input$force, which can take in a list of strings from a selectInput field.
Lastly, the function that I am using to save the user inputs is saving the action buttons, and doing so in an odd format. These values are not loading properly when utilizing the load action button. Here is a sample of the code I am using to attempt this:
observeEvent(input$load,{
if(!file.exists('inputs.RDS')) {return(NULL)}
savedInputs <- readRDS('inputs.RDS')
inputIDs <- names(savedInputs)
inputvalues <- unlist(savedInputs)
for (i in 1:length(savedInputs)) {
session$sendInputMessage(inputIDs[i], list(value=inputvalues[[i]]) )
}
})
observeEvent(input$save,{
saveRDS( reactiveValuesToList(input) , file = 'inputs.RDS')
})
Thank you in advance for the help!
I'm developing a shiny application where the user can upload a file and select X parameters, after that he press a button and it generates 5 plots (ggplot2 and barplot) and also a dynamic data table (DT). Also, I want to put my shiny app into a linux server.
I'm using tempfiles() for each file I'm using to create the plots and also the DT.
After that, my question is:
When the user closes the shiny app (close the window), do the tempfiles autodelete?
If not, what can I do to delete the tempfiles?
My tries:
session$onSessionEnded(function() {
if (!is.null(x1)) {
file.remove(x1)
}
if (!is.null(x2)) {
file.remove(x2)
}
if (!is.null(x3)) {
file.remove(x3)
}
if (!is.null(x4)) {
file.remove(x4)
}
if (!is.null(xx)) {
file.remove(xx)
}
})
Or:
session$onSessionEnded(function() {
files <- list.files(tempdir(), full.names = T, pattern = "^file")
file.remove(files)
})
With that code I delete the tempfiles when user presses the button once, and if the user presses the button more than 1 time then the window closes, and it will only delete the last generated files. The second part deletes all files at the temp dir but that affects to the other users?(I think yes so that's why I need another solution).
The .png tempfiles generated by ggplot and barplot doesn't autodelete.
My worry is that if the tempfiles won't autodelete and the linux server will collapse because of a lot of tempfiles.
Hope you can solve my doubts. Att Joan.
You can use the deleteFile=TRUE parameter if you want a render function to automatically delete your temporary files:
shinyServer(function(input, output, clientData) {
output$myImage <- renderImage({
# A temp file to save the output.
# This file will be removed later by renderImage
outfile <- tempfile(fileext='.png')
# Generate the PNG
png(outfile, width=400, height=300)
hist(rnorm(input$obs), main="Generated in renderImage()")
dev.off()
# Return a list containing the filename
list(src = outfile,
contentType = 'image/png',
width = 400,
height = 300,
alt = "This is alternate text")
}, deleteFile = TRUE)
})
A temp file is created to save the output, and that file is later automatically removed because of the deleteFile=TRUE argument.
The default Shiny (shiny.R) also has a built-in mechanism that clear file upload directories if that's your concern. The following code remove the upload directory when the session ends:
registerSessionEndCallbacks = function() {
# This is to be called from the initialization. It registers functions
# that are called when a session ends.
# Clear file upload directories, if present
self$onSessionEnded(private$fileUploadContext$rmUploadDirs)
}
Another point regarding manually deleting your temp files (as what you were attempting): the plot will have to render every time the user switched to another tab or resize his / her browser window, so if you're manually deleting the file, it may be inefficient since it needs to be re-rendered again. The onSessionEnded solution is nicer as it confirms that the session has ended.
session$onSessionEnded(function() {
if (!is.null(input$file1)) {
file.remove(input$file1$datapath)
}
})
I am using Shiny as an interface for viewing tables stored locally in a series of .RData files however I am unable to get the table to render.
My server code is like this:
output$table1 <- renderTable({
load(paste0(input$one,"/",input$two,".RData"))
myData})
On the ui side I am simply displaying the table in the main panel.
This other SO question suggests that the issue is that the environment that the data is loaded into goes away so the data isn't there to display. They suggest creating a global file and loading the .RData file in there, but I don't believe I will be able to load the data dynamically that way. Any guidance on how to use .RData files effectively within shiny would be appreciated.
Regards
I think you just need to move the load statement outside of the renderTable function. So you should have
load(paste0(input$one,"/",input$two,".RData"))
output$table1 <- renderTable({myData})
If you look at the help file for renderTable, the first argument is
expr: An expression that returns an R object that can be used with
xtable.
load does not return this.
I got around this by "tricking" R Shiny. I make a BOGUS textOutput, and in renderText, call a external function that, based in the input selected, sets the already globally loaded environments to a single environment called "e". Note, you MUST manually load all RDatas into environments in global.R first, with this approach. Assuming your data isn't that large, or that you don't have a million RDatas, this seems like a reasonable hack.
By essentially creating a loadEnvFn() like the below that returns a string input passed as input$datasetNumber, you can avoid the scoping issues that occur when you put code in a reactive({}) context. I tried to do a TON of things, but they all required reactive contexts. This way, I could change the objects loaded in e, without having to wrap a reactive({}) scope around my shiny server code.
#Global Environment Pre-loaded before Shiny Server
e = new.env()
dataset1 = new.env()
load("dataset1.RData", env=dataset1)
dataset2 = new.env()
load("dataset2.RData", env=dataset2)
dataset3 = new.env()
load("dataset3.RData", env=dataset3)
ui = fluidPage(
# Application title
titlePanel(title="View Datasets"),
sidebarLayout(
# Sidebar panel
sidebarPanel(width=3, radioButtons(inputId = "datasetNumber", label = "From which dataset do you want to display sample data?", choices = list("Dataset1", "Dataset2", "Dataset3"), selected = "Dataset2")
),
# Main panel
mainPanel(width = 9,
textOutput("dataset"), # Bogus textOutput
textOutput("numInEnv")
)
)
)
loadEnvFn = function(input) {
if (input$datasetNumber=="Dataset1") {
.GlobalEnv$e = dataset1
} else if (input$datasetNumber=="Dataset2") {
.GlobalEnv$e = dataset2
} else {
.GlobalEnv$e = dataset3
}
# Bogus return string unrelated to real purpose of function loadEnvFn
return(input$datasetNumber)
}
server = function(input, output, session) {
output$dataset = renderText(sprintf("Dataset chosen was %s", loadEnvFn(input))) # Bogus output
output$numInEnv = renderText(sprintf("# objects in environment 'e': %d", length(ls(e))))
}
shinyApp(ui, server)