I am relying on the code chunk found here to create a Shiny app to upload a table, edit the table, then download the table. I have managed to edit a table that is already loaded in memory (iris) but how do I edit a table that is to be uploaded in Shiny?.
I have tried the code in the link above and verified that it works. I have also tried the code below and this works too. What I have not been able to achieve is to convert the data frame x
into a reactive object that is assigned to an uploaded file, and edit all the references to x
accordingly.
# This code works, but lacks a fileinput object
# and needs to be amended for a reactive dataframe...
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
fluidRow(
# ~~ add fileInput("file1", "Choose file") here ~~
downloadButton("download")
),
fluidRow(
DT::dataTableOutput('x1')
)
),
server = function(input, output, session) {
# Do I make x reactive?
x = iris
x$Date = Sys.time() + seq_len(nrow(x))
output$x1 = DT::renderDataTable(x, selection = 'none', rownames = FALSE, edit = TRUE)
proxy = dataTableProxy('x1')
observeEvent(input$x1_cell_edit, {
info = input$x1_cell_edit
str(info)
i = info$row
j = info$col + 1
v = info$value
x[i, j] <<- DT:::coerceValue(v, x[i, j])
replaceData(proxy, x, resetPaging = FALSE, rownames = FALSE)
})
output$download <- downloadHandler("example.csv",
content = function(file){
write.csv(x, file)
},
contentType = "text/csv")
}
)
Previous attempts at this have thrown errors, mostly to do with operations not being allowed without an active reactive context.
The code below shows what I want to achieve, but throws an error: "argument "expr" is missing, with no default"
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
fluidRow(
fileInput("upload", "Choose CSV File",
multiple = FALSE,
accept = c("text/csv",
"text/comma-separated-values,text/plain",
".csv")),
downloadButton("download")
),
fluidRow(
DT::dataTableOutput('x1')
)
),
server = function(input, output, session) {
#x = iris
# In this edited example x is now a reactive expression, dependent on input$upload
x <- eventReactive({
# input$file1 will be NULL initially. After the user selects
# and uploads a file, head of that data file by default,
# or all rows if selected, will be shown.
req(input$upload)
# when reading semicolon separated files,
# having a comma separator causes `read.csv` to error
tryCatch(
{
x <- read.csv(input$upload$datapath,
header = TRUE,
sep = ",",
stringsAsFactors = TRUE,
row.names = NULL)
},
error = function(e) {
# return a safeError if a parsing error occurs
stop(safeError(e))
}
)
})
#x$Date = Sys.time() + seq_len(nrow(x))
output$x1 = DT::renderDataTable(x(), selection = 'none', rownames = FALSE, edit = TRUE)
proxy = dataTableProxy('x1')
observeEvent(input$x1_cell_edit, {
info = input$x1_cell_edit
str(info)
i = info$row
j = info$col + 1
v = info$value
x()[[i, j]] <<- DT:::coerceValue(v, x()[[i, j]])
newdf <- x()
replaceData(proxy, newdf, resetPaging = FALSE, rownames = FALSE)
})
output$download <- downloadHandler("example.csv",
content = function(file){
write.csv(x(), file)
},
contentType = "text/csv")
}
)
With thanks to Stephane, and inspiration from this related question, I think I have an answer.
Key is to use reactiveValues as a workaround to DT:::coerceValue not liking reactive expressions. I've included a verbatimTextOutput to illustrate the stored changes to the table after you edit the data table. The download button allows you to download the edited table too.
See
?eventReactive
.You should do:
or