Cross-posted from https://community.rstudio.com/t/reactive-input-to-module/143679, if that's not okay feel free to let me know! I'm new to posting here.
I'm using a module to handle file uploading. It allows the user to upload a CSV or an RDS, or to use the dataframe produced in a previous stage of the app. The call to the module takes the name of this previous dataframe as an argument, to know what to return if the user selects this option.
My issue is that this previous dataframe doesn't seem to update reactively. For one of the steps (we'll call it step 3), users can select whether they want to continue by using the dataframe from step 1, or from step 2. I've tried to code this by creating a server-side object, for_use_prev(), which stores the DF from step 1 if a checkbox is checked, and stores the DF from step 2 of the checkbox is unchecked. for_use_prev() is then called by the module.
The module call, however, does not update when for_use_prev() changes. It only takes the original value of the dataframe, and does not reset even when for_use_prev() changes. Clicking the upload button again also does not force it to take the new value of for_use_prev().
Why does the module call not change reactively to the reactive dataframe? I have tried various ways of calling it:
without parentheses:
loadFileServer("calc_input", prev_file=for_use_prev)
with parentheses:
loadFileServer("calc_input", prev_file=for_use_prev())
wrapped in reactive, with and without parentheses:
loadFileServer("calc_input", prev_file=reactive(for_use_prev()))
loadFileServer("calc_input", prev_file=reactive(for_use_prev))
None of them change the module output, although for_use_prev() itself is definitely changing. Additionally, when for_use_prev is called without parentheses as suggested here, the module returns the function behind for_use_prev rather than the dataframe.
Wrapping the whole module call in reactive() also does not work.
Does anyone have an idea how I can get the module call to react to changing input?
Below is a minimal example. To reproduce the issue, you can upload any two random CSV files in steps 1 and 2. In step 3, the current value of for_use_prev() is shown under "Current dataframe for_use_prev() is using". When you click the action button "Use file from previous step", the file upload module should output the same dataframe as for_use_prev() which will be displayed next to for_use_prev(). It does this for the first value of for_use_prev(), but if you uncheck the checkbox above the action button, you should be able to observe that for_use_prev() changes accordingly, but the value doesn't change from its initial one even as for_use_prev() changes.
EDIT: This is what it looks like when using the reactive (without parentheses) instead of its value (with parentheses).
The module returns the function rather than the dataframe.
I'm reluctant to change the module itself, since it's used multiple times throughout my app, and this is the only instance in which the user must be given a choice between two different previous DFs.
If more details or explanation are necessary please let me know!
Reprex:
library(shiny)
library(shinydashboard)
# Define the module
# Module UI function
loadFileUI <- function(id) {
# `NS(id)` returns a namespace function, which was save as `ns` and will
# invoke later.
ns <- NS(id)
tagList(
actionButton(ns("file_from_prev"),"Use file from previous step"),
h5("Or upload a saved file:"),
fileInput(ns("file_rds"), "RDS file",accept=".rds"),
fileInput(ns("file_csv"),"CSV File",accept=".csv"),
actionButton(ns("file_load_rds"),"Load RDS"),
actionButton(ns("file_load_csv"),"Load CSV"),
actionButton(ns("file_clear"),"Remove file upload")
)
}
# Module server function
loadFileServer <- function(id, prev_file) {
moduleServer(
id,
## Below is the module function
function(input, output, session) {
# initiate reactive values object to store what type of upload you want, or to clear your upload
upload_file <- reactiveValues(state=NULL)
observeEvent(input$file_from_prev,{ # take file from previous step
upload_file$state <- "prev"
})
observeEvent(input$file_load_rds,{ # load file from rds
upload_file$state <- "rds"
})
observeEvent(input$file_load_csv,{ # load file from csv
upload_file$state <- "csv"
})
observeEvent(input$file_clear,{ # clear file
upload_file$state <- "clear"
})
# actually upload the file (source depends on setting of upload_file$state as set above)
file_full <- reactive(
if(upload_file$state=="prev"){
prev_file
} else if(upload_file$state=="rds" & !is.null(input$file_rds)){
readRDS(input$file_rds$datapath)
} else if(upload_file$state=="csv" & !is.null(input$file_csv)){
read.csv(input$file_csv$datapath)
} else if(upload_file$state=="clear"){
NULL
}
)
# Return the reactive that yields the data frame
return(
list(df=(file_full),
status=reactive(upload_file$state))
)
}
)
}
# Set up the app
ui <- dashboardPage(
dashboardHeader(
title = "Reactive module input"
),
dashboardSidebar(
sidebarMenu(
menuItem("Step 1",tabName = "upload1"),
menuItem("Step 2",tabName = "upload2"),
menuItem("Step 3",tabName = "upload3")
)
),
dashboardBody(
tabItems(
tabItem(
tabName = "upload1",
fileInput("file_up1",label = "Upload file",accept = ".csv"),
tableOutput("input1_preview")
),
tabItem(
tabName = "upload2",
fileInput("file_up2",label = "Upload file",accept = ".csv"),
tableOutput("input2_preview")
),
tabItem(
tabName = "upload3",
fluidRow(
column(width = 6,
box(width = NULL,
checkboxInput("which_prev_input","If checked, use input 1 as previous, otherwise input 2",value=T),
loadFileUI("step3_input")
)
)
),
fluidRow(
column(width = 6,
box(width = NULL,
title = "Current dataframe for_use_prev() is using",
tableOutput("prev_df_preview")
)
),
column(width = 6,
box(width = NULL,
title = "Dataframe being uploaded by the module",
tableOutput("step3_preview")
)
)
)
)
)
)
)
server <- function(input, output) {
# First file upload
upload1 <- reactive({
read.csv(input$file_up1$datapath)
})
output$input1_preview <- renderTable(upload1())
# Second file upload
upload2 <- reactive({
read.csv(input$file_up2$datapath)
})
output$input2_preview <- renderTable(upload2())
# Choose whether to use the first or second file
for_use_prev <- reactive({
if(input$which_prev_input){
upload1()
} else{
upload2()
}
})
# Call file upload module to give the possibility to upload a CSV, RDS, or use a previously uploaded file
upload_step3_raw <- loadFileServer("step3_input", prev_file=for_use_prev()) # the call to for_use_prev doesn't update
upload_step3_df <- reactive(upload_step3_raw$df())
# Preview the DF chosen to be the previous dataframe (for_use_prev)
output$prev_df_preview <- renderTable(head(for_use_prev()))
# Preview the uploaded dataframe
output$step3_preview <- renderTable(head(upload_step3_df()))
}
shinyApp(ui, server)
sessionInfo:
R version 4.2.1 (2022-06-23 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19042)
Matrix products: default
locale:
[1] LC_COLLATE=English_Germany.utf8 LC_CTYPE=English_Germany.utf8 LC_MONETARY=English_Germany.utf8
[4] LC_NUMERIC=C LC_TIME=English_Germany.utf8
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] shinydashboard_0.7.2 shiny_1.7.1
loaded via a namespace (and not attached):
[1] Rcpp_1.0.8 jquerylib_0.1.4 bslib_0.3.1 later_1.3.0 pillar_1.7.0 compiler_4.2.1
[7] plyr_1.8.6 bitops_1.0-7 tools_4.2.1 digest_0.6.29 jsonlite_1.7.3 lifecycle_1.0.1
[13] tibble_3.1.6 gtable_0.3.0 pkgconfig_2.0.3 rlang_1.0.1 cli_3.1.1 DBI_1.1.2
[19] fastmap_1.1.0 dplyr_1.0.8 httr_1.4.2 xml2_1.3.3 sass_0.4.0 generics_0.1.2
[25] vctrs_0.3.8 htmlwidgets_1.5.4 grid_4.2.1 tidyselect_1.1.2 fontawesome_0.2.2 reshape_0.8.8
[31] glue_1.6.2 data.table_1.14.2 R6_2.5.1 fansi_1.0.2 purrr_0.3.4 ggplot2_3.3.5
[37] magrittr_2.0.3 promises_1.2.0.1 scales_1.1.1 ellipsis_0.3.2 htmltools_0.5.2 assertthat_0.2.1
[43] rvest_1.0.2 xtable_1.8-4 mime_0.12 colorspace_2.0-2 httpuv_1.6.5 utf8_1.2.2
[49] munsell_0.5.0 RCurl_1.98-1.6 cachem_1.0.6 crayon_1.5.0
I'm not entirely clear what you are trying to do, and your reprex contained many small errors (most notably the incorrect definition of the return value from the upload server function and confusion between a reactive function (myReactive) and its current value (myReactive()), but this is my best guess at what you want.
On the Step 3 tab:
The left hand box ("Current dataframe for_use_prev() is using") updates depending on whether the "If checked ..." chekbox is checked or not
The right hand box is initially empty
The right hand box displays the same data as the left hand box when the "use file from previous step" button is clicked and updates in response to checking and unchecking the "If checked..." checkbox
The right hand box displays different data to the right hand box once the "Load CSV" button is clicked after loading a third scv file in the "CSV file" fileInput.
The right hand checkbox is empty after the "remove file upload" button is checked.
I believe all I have done is implement the changes I indicated were necessary in my original comment.
library(shiny)
library(shinydashboard)
# Define the module
# Module UI function
loadFileUI <- function(id) {
# `NS(id)` returns a namespace function, which was save as `ns` and will
# invoke later.
ns <- NS(id)
tagList(
actionButton(ns("file_from_prev"),"Use file from previous step"),
h5("Or upload a saved file:"),
fileInput(ns("file_rds"), "RDS file",accept=".rds"),
fileInput(ns("file_csv"),"CSV File",accept=".csv"),
actionButton(ns("file_load_rds"),"Load RDS"),
actionButton(ns("file_load_csv"),"Load CSV"),
actionButton(ns("file_clear"),"Remove file upload")
)
}
# Module server function
loadFileServer <- function(id, prev_file) {
moduleServer(
id,
## Below is the module function
function(input, output, session) {
# initiate reactive values object to store what type of upload you want, or to clear your upload
upload_file <- reactiveValues(state=NULL)
observeEvent(input$file_from_prev,{ # take file from previous step
upload_file$state <- "prev"
})
observeEvent(input$file_load_rds,{ # load file from rds
upload_file$state <- "rds"
})
observeEvent(input$file_load_csv,{ # load file from csv
upload_file$state <- "csv"
})
observeEvent(input$file_clear,{ # clear file
upload_file$state <- "clear"
})
# actually upload the file (source depends on setting of upload_file$state as set above)
file_full <- reactive(
if(upload_file$state=="prev"){
prev_file()
} else if(upload_file$state=="rds" & !is.null(input$file_rds)){
readRDS(input$file_rds$datapath)
} else if(upload_file$state=="csv" & !is.null(input$file_csv)){
read.csv(input$file_csv$datapath)
} else if(upload_file$state=="clear"){
NULL
}
)
rv <- reactive({
req(input$file_from_prev)
list(df=file_full(), status=upload_file$state)
})
# Return the reactive that yields the data frame
return(rv)
}
)
}
# Set up the app
ui <- dashboardPage(
dashboardHeader(
title = "Reactive module input"
),
dashboardSidebar(
sidebarMenu(
menuItem("Step 1",tabName = "upload1"),
menuItem("Step 2",tabName = "upload2"),
menuItem("Step 3",tabName = "upload3")
)
),
dashboardBody(
tabItems(
tabItem(
tabName = "upload1",
fileInput("file_up1",label = "Upload file",accept = ".csv"),
tableOutput("input1_preview")
),
tabItem(
tabName = "upload2",
fileInput("file_up2",label = "Upload file",accept = ".csv"),
tableOutput("input2_preview")
),
tabItem(
tabName = "upload3",
fluidRow(
column(width = 6,
box(width = NULL,
checkboxInput("which_prev_input","If checked, use input 1 as previous, otherwise input 2",value=T),
loadFileUI("step3_input")
)
)
),
fluidRow(
column(width = 6,
box(width = NULL,
title = "Current dataframe for_use_prev() is using",
tableOutput("prev_df_preview")
)
),
column(width = 6,
box(width = NULL,
title = "Dataframe being uploaded by the module",
tableOutput("step3_preview")
)
)
)
)
)
)
)
server <- function(input, output) {
# First file upload
upload1 <- reactive({
req (input$file_up1)
read.csv(input$file_up1$datapath)
})
output$input1_preview <- renderTable(upload1())
# Second file upload
upload2 <- reactive({
req (input$file_up2)
read.csv(input$file_up2$datapath)
})
output$input2_preview <- renderTable(upload2())
# Choose whether to use the first or second file
for_use_prev <- reactive({
if(input$which_prev_input){
upload1()
} else{
upload2()
}
})
# Call file upload module to give the possibility to upload a CSV, RDS, or use a previously uploaded file
upload_step3_raw <- loadFileServer("step3_input", prev_file=for_use_prev) # the call to for_use_prev doesn't update
upload_step3_df <- reactive({ upload_step3_raw()$df })
# Preview the DF chosen to be the previous dataframe (for_use_prev)
output$prev_df_preview <- renderTable(head(for_use_prev()))
# Preview the uploaded dataframe
output$step3_preview <- renderTable({
req(upload_step3_df())
head(upload_step3_df())
})
}
shinyApp(ui, server)
You may very well have to change the module because the way you defined its return value was, I believe, fundamentally incorrect because the original definition did not allow other parts of the app to respond reactively.
One way of avoiding this situation arising in the future is to thoroughly test that the way the module is behaving is correct before beginning to use it in many different places within your app.
Welcome to SO.
Related
super new to shiny, have a problem that seems like it should be basic reactive programming but I haven't been able to find a solution that's worked so far.
Essentially, I want to take the user's selected input from the UI and paste it into a simple object in the server that will react/update when a new input is chosen.
The object will be concatenated into a full API call, and I wish to rerun the API call in the server with the reactive object updated each time a new input is chosen for it (note: the API cannot be run without an access code which is part of a corporate account, so apologies for my hesitance to put my full code but I just need help with this one functionality.)
In code below:
with Dollar General as the default selection in the selectInput, I would like the object, query, to be the character string "dollar%20general", and reactively change to "walmart" should Walmart be selected
Thanks!
ui <- fluidPage
sidebarLayout(
sidebarPanel(
selectInput("company", "Choose company:",
c("Dollar General" = "dollar%20general",
"Dollar Tree" = "dollar%20tree",
"Walmart" = "walmart"))
...
server <- function(input,output) {
...
query <- paste(input$company)
...
you can use reactiveValues() and observe. This should work:
library(shiny)
# Define UI for application
ui <- fluidPage(
# your input
sidebarLayout(
sidebarPanel(
selectInput("company", "Choose company:",
c("Dollar General" = "dollar%20general",
"Dollar Tree" = "dollar%20tree",
"Walmart" = "walmart"))
),
# Determine Output
mainPanel(
textOutput("showInput") # you need to render this in your server file
)
)
)
server <- function(input, output) {
# Show what was selected
query <- reactiveValues()
observe(
query$test <- paste(input$company, "and test", sep = " ")
)
output$showInput <- renderText({ #based on what you defined in the ui
query$test
})
}
# Run the application
shinyApp(ui = ui, server = server)
Create two files named ui.R and server.R store the UI logic in ui.R and backend/object logic in server.R. Below is the implementation.
UI file
# UI of app
ui <- fluidPage(
# input
sidebarLayout(
sidebarPanel(
selectInput("company", "Choose company:",
c("Dollar General" = "dollar%20general",
"Dollar Tree" = "dollar%20tree",
"Walmart" = "walmart"))
),
# Output
mainPanel(
textOutput("Input")
)
)
)
Server/Backend File
server <- function(input, output) {
# Show what was selected
output$Input <- renderText({ #based on what you defined in the ui
input$company
})
}
Now store these in a directory and then call runApp function.
~/newdir
|-- ui.R
|-- server.R
runApp("newdir")
I want to upload updated csv file daily basis. Once the csv file get uploaded, the upload icon should disappear and valueBox should display with relevant value. Here is the below code:
library(shiny)
library(shinydashboard)
# Define UI for application that draws a histogram
ui <- dashboardPage(
dashboardHeader(title = "Upload Stats"),
dashboardSidebar(),
dashboardBody(
box(
title = "UPTIME:", width = 12,
div(column(width = 4, fileInput(inputId = "file", label = "import", accept = ".csv")),
column(width = 8, valueBoxOutput("stats"))
)
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$stats <- renderValueBox({
req(input$file)
data <- read.csv(input$file$datapath)
valueBox("scr1", sum(data[,2]), width = 12)
})
}
# Run the application
shinyApp(ui = ui, server = server)
The above code accepting csv file each time visiting the shinydashboard. Currently it showing the upload icon each time someone opens the URL/dashboard. I want the upload icon should shown till csv file not uploaded into shinyApp. Once uploaded, it should disappear and should display 'valueBox()' with values depend on the uploaded file.
Can someone help me how to write that control code?
Since your application is going to be used by multiple people who can access the URL, the simple way would be to create a global .rds file accessible by all the users whenever the .csv file is imported.
data <- read.csv(input$file$datapath)
# Create a folder named srcdata under www folder in your app directory
# Save the data object as a .rds file with system date appended to the file name
saveRDS(data,paste0("www/srcdata/data_",Sys.Date()))
However, we would need to create this .rds file only once per day. If a file already exists for the current date, we can
1. Skip this step and read the file directly
2. Hide the input field from the UI
So the code becomes
filePresent <- list.files("www/srcdata/", pattern = paste0("data_",Sys.Date()))
# if file is present, disable the input field and read from the saved .rds
# if file is not present, show the input field
if(length(filePresent)==1){
data <- readRDS(paste0("www/srcdata/data_",Sys.Date()))
filedata$notPresent <- FALSE
}else{
shinyjs::show("file")
}
Here, we are using shinyjs to show and hide the fields. So you would need to install that package (if not already) and call it in your code. Also, this code should run every time the app gets initialized so that the users either get presented with data (if there is a saved file) or sees a input field to import the file.
I have updated the code to implement this
library(shiny)
library(shinydashboard)
library(shinyjs)
# Define UI for application that draws a histogram
ui <- dashboardPage(
dashboardHeader(title = "Upload Stats"),
dashboardSidebar(),
dashboardBody(
useShinyjs(),
box(
title = "UPTIME:", width = 12,
div(column(width = 4, hidden(fileInput(inputId = "file", label = "import", accept = ".csv"))),
column(width = 8, valueBoxOutput("stats"))
)
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
filedata <- reactiveValues(notPresent = TRUE)
observeEvent(filedata$notPresent,{
if(filedata$notPresent){
filePresent <- list.files("www/srcdata/", pattern = paste0("data_",Sys.Date()))
if(length(filePresent)==1){
data <- readRDS(paste0("www/srcdata/data_",Sys.Date()))
filedata$notPresent <- FALSE
}else{
shinyjs::show("file")
}
}
})
output$stats <- renderValueBox({
req(input$file)
data <- read.csv(input$file$datapath)
saveRDS(data,paste0("www/srcdata/data_",Sys.Date()))
valueBox("scr1", sum(data[,2]), width = 12)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Hope this helps!
I'm having an issue with reactivity when using modules in R. If I update a module and then try to update another module with those updated values, I instead get the values prior to the update.
I've written up some basic code to show what I mean below. Here I have an app that updates a rHandsontableOutput placed in a module called my_module and then copies this updated rHandsontableOutput to a second module called module_to_update when a button is pressed.
What I'm finding is that the first table in my_module will update but not the one in module_to_update. Instead, the module_to_update table will receive a copy of my_module's initial table prior to the update. If I press the update button again, things work as expected.
I'm guessing this is an issue with either how I'm handling the session or reactive values generally, but I'm out of ideas.
QUESTION: How can I set up reactive values and modules such that I can run operations on updated module data within the same function call? (e.g. see the observeEvent(input$update_btn, ...) call below for an example)
Image:
application.R
library(shiny)
library(rhandsontable)
source('my_modules.R')
active_tab = ""
ui <- navbarPage("Module Test Tool",
tabsetPanel(id = 'mainTabset',
tabPanel("My Tab",
#This is the HoT that works as expected, updating when called upon
h4("Table 1"),
myModuleUI('my_module'),
#This is the HoT that does NOT work as expected. This HoT fails to use the updated values from 'my_module' HoT
h4("Table to be updated"),
myModuleUI('module_to_update'),
br(),
br(),
fluidRow(
#this button updates tables to new values
actionButton("update_btn", "Update and Add Tables"),
br(),
br(),
textOutput('table1_sum'),
textOutput('table2_sum'),
br(),
br()
)
)
)
)
server <- function(input, output, session) {
#Link logic for tab module
callModule(myModule, 'my_module')
#This button sums up all the rHandsonTable data frames
observeEvent(input$update_btn, {
#Update values in table and integer drop down list before doing basic operations on them
#New values should be all 5s
five_col = rep(5,3)
callModule(updateModule, 'my_module', 5, data.frame(col1 = five_col,
col2 = five_col,
col3 = five_col))
#Grabs updated module table and does operations on it
module_data = callModule(getMyModuleData, 'my_module')
module_int= module_data$module_int
module_df = module_data$module_df
output$table1_sum = renderText({
paste0("Sum of Table 1 is: ", sum(module_df())," | The selected integer is: ", module_int())
})
#------------------------------------------------------
#------------------ERROR BELOW-------------------------
#------------------------------------------------------
#THIS IS THE CODE THAT FAILS. This updates a 2nd module that should mirror the updated values. However, this results in old values.
callModule(updateModule, 'module_to_update', module_int(), module_df())
#Tries to call on new, updated table
updated_module_data = callModule(getMyModuleData, 'module_to_update')
updated_module_int= updated_module_data$module_int
updated_module_df = updated_module_data$module_df
#Display results of basic operations on new table
output$table2_sum = renderText({
paste0("Sum of Updated Table is: ", sum(updated_module_df())," | The selected integer is: ", updated_module_int())
})
})
}
## Create Shiny app ----
shinyApp(ui, server)
my_modules.R
#Simple module containing one rHandsontable and a drop down list of integers
myModuleUI <- function(id,tab_name){
ns <- NS(id)
fluidRow(
rHandsontableOutput(ns("module_hot")),
selectInput(ns('module_int_list'),"Integers:",c(1:5), selected = 1)
)
}
#Initializes myModuleUI rHandsonTable with some values
myModule <- function(input, output, session) {
one_col = rep.int('VALUE AT INITIALIZATION',3)
df = data.frame(col1 = one_col,
col2 = one_col,
col3 = one_col)
output$module_hot <- renderRHandsontable({
rhandsontable(df, stretchH = "none", rowHeaders = NULL)
})
}
#Returns myModule data for use outside of the module
getMyModuleData <- function(input,output,session){
return (
list(
module_df = reactive({hot_to_r(input$module_hot)}),
module_int = reactive({input$module_int_list})
)
)
}
updateModule<- function(input,output,session, new_integer, new_dataframe){
if(!is.null(new_dataframe))
{
output$module_hot <- renderRHandsontable({
rhandsontable(new_dataframe, stretchH = "none", rowHeaders = NULL)
})
}
outputOptions(output, "module_hot", suspendWhenHidden = FALSE)
updateSelectInput(session, "module_int_list",selected = new_integer)
}
There are a few problems in here...
You are calling multiple different modules with the same namespace. Modules are supposed to operate independently of each other. They should each have their own namespace. The following are not correct:
callModule(myModule, 'my_module')
callModule(updateModule, 'my_module', 5, data.frame(col1 = five_col,
col2 = five_col,
col3 = five_col))
module_data = callModule(getMyModuleData, 'my_module')
You are calling modules from within observeEvent(). This means every time you observe that event you try to initialize that module. You don't want to initialize the module, you want to pass the new variables to that module. If you make a module return it's values, then use those returned values as inputs into another module you won't need to observe the event...the module that receives the new information will decide whether to observe the change.
You have created a function/module getMyModuleData that is only supposed to return data that is present in a different module. Instead you should have the other module return the data you want.
Check out: https://shiny.rstudio.com/articles/communicate-bet-modules.html.
The following code produces a GUI where an user can input files paths. The code pre-processes those file paths and safes the results. The idea is that those inputs are passed to a render function for Rmarkdown and, on the first action button press, sends those values to the render function such that another Rmarkdown file is rendered that uses those values.
That is how it works in theory, in practice the first button press causes a render to happen, but non of the params are actually sent along with the first button press. However, the second button press on, everything works fine. Can anyone see the flaw in my code, that is preventing the first button press to behave the way I want it to?
I have tried looking around to see if someone else has figured this out and honestly all I think i figured out is maybe I use isolate()? sorry i am very new to R shiny.
GUIforRmarkdowngeneration
title: "Generate Rmarkdown Report for data"
runtime: shiny
output: html_notebook
#setwd("/home/alan/Desktop/wts/filestosaveandcarryover/tiamatfiles")
knitr::opts_chunk$set(echo=TRUE)
library(reticulate) # <- only needed to run python
#use_python("/Users/dyarmosh/anaconda3/bin/python")
library(shinyFiles)
library(glue)
library(stringr)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
shinyFilesButton("Btn_GetFile", "Choose forward fastq" ,
title = "Please select a file:", multiple = FALSE,
buttonType = "default"),
br(),
selectInput("Selected_Panel", "Select Panel", choices = c("bacillus","burkholderia","yersinia")),
br(),
# #submitButton("Begin Analysis") - fix this no select item from file system
actionButton("Start", "Begin Analysis")
),
mainPanel(
br(),
textOutput("fwdftext"),
br(),
textOutput("revftext"),
br(),br(),
textOutput("paneltext"),
br(),br(),
textOutput("completion")
))
)
rvs <- reactiveValues() #renamed to rvs
server <- function(input,output,session){
volumes = getVolumes()
observe({
shinyFileChoose(input, "Btn_GetFile", roots = volumes, session = session)
req(input$Btn_GetFile)
req(input$Selected_Panel)
req(input$Start)
fwd_selected <- parseFilePaths(volumes, input$Btn_GetFile)
rvs$fwdfastq <- as.character(fwd_selected$datapath)
if (str_detect(rvs$fwdfastq,"R1")){rvs$revfastq <- str_replace(rvs$fwdfastq,"R1","R2")}
if (str_detect(rvs$fwdfastq,"R2")){rvs$revfastq <- str_replace(rvs$fwdfastq,"R2","R1")}
class_selected <- parseFilePaths(volumes, input$ClassifierFile)
kmer_selected <- parseFilePaths(volumes, input$StoredKmerFile)
rvs$panopt <- as.character(input$Selected_Panel)
rvs$start <- input$Start
output$fwdftext <- renderText({paste("Entered Forward File:", rvs$fwdfastq)})
output$revftext <- renderText({paste("Matched Reverse File:", rvs$revfastq)})
output$paneltext <- renderText({paste("Entered Panel:", rvs$panopt)})
})
observeEvent(input$Start, {
print(paste(rvs$panopt,rvs$fwdfastq,rvs$revfastq, rvs$class,rvs$kmer))
rmarkdown::render("testinput.Rmd",
params = list(
panelname = rvs$panopt,
r1filename = rvs$fwdfastq,
r2filename = rvs$revfastq,
classifier = rvs$class,
kmer = rvs$kmer
),
output_file = paste0("AmpSeqClass_Report_",strsplit(basename(toString(rvs$fwdfastq)),"[.]")[[1]][1],"_",str_replace(c(Sys.time())," ","_"),".html"))
output$completion <- renderText({paste("Rmarkdown Document render is complete at ",toString(Sys.time()))})
})
}
observe({
req(rvs$fwdfastq)
req(rvs$revfastq)
req(rvs$panopt)
req(rvs$start)
})
shinyApp(ui = ui, server = server)
testinputfile.Rmd
title: Perform stuff
output:
html_document:
params:
r1filename:
label: "Forward:"
value: BurkP-130611_S12_100_L001_R1_001.fastq
r2filename:
label: "Reverse:"
value: BurkP-130611_S12_100_L001_R2_001.fastq
panelname:
label: "AP:"
value: bacillus
input: select
choices: [bacillus,burkholderia,yersinia]
setwd("/home/alan/Desktop/wts/filestosaveandcarryover/tiamatfiles")
knitr::opts_chunk$set(echo=TRUE)
library(reticulate) # <- only needed to run python
#use_python("/Users/dyarmosh/anaconda3/bin/python")
print(params)
basically if you can get the rmarkdown to generate properly on the first action button click, with non of the passed params being NULL, that is the solution
Instead of specifying separate fileInput variables, I'd like to use reactiveValues to store uploaded CSV dataframes, manipulate them in some way, and then store them for accession later. My design is to name each dataframe by its filename and append to the reactiveValue rvTL. My questions are,
How can I access individual dataframes under the list I created using reactiveValuesToList(rvTL)?
Next step, how to create a selectInput menu to access the individual dataframes uploaded by fileInput
To learn this concept, I am piggybacking off the answer from Dean Attali and made rvTL the same as his values variable.
R shiny: How to get an reactive data frame updated each time pressing an actionButton without creating a new reactive data frame?
I've gone over many example codes on reactiveValues, yet still at an incomplete understanding. Most examples are using some sort variation on reactiveValuesToList(input) R Shiny: Keep/retain values of reactive inputs after modifying selection, I'm really not seeing the logic here. Any help/suggestions would be appreciated!
library(shiny)
runApp(shinyApp(
ui=(fluidPage(
titlePanel("amend data frame"),
mainPanel(
fileInput("file", "Upload file", multiple=T),
tabsetPanel(type="tabs",
tabPanel("tab1",
numericInput("Delete", "Delete row:", 1, step = 1),
actionButton("Go", "Delete!"),
verbatimTextOutput("df_data_files"),
verbatimTextOutput("values"),
verbatimTextOutput("rvTL"),
tableOutput("rvTL_out")
),
tabPanel("tab2",
tableOutput("df_data_out")
)
)))),
server = (function(input, output) {
values <- reactiveValues(df_data = NULL) ##reactiveValues
rvTL <- reactiveValues(rvTL = NULL)
observeEvent(input$file, {
values$df_data <- read.csv(input$file$datapath)
rvTL[[input$file$name]] <- c(isolate(rvTL), read.csv(input$file$datapath))
})
observeEvent(input$Go, {
temp <- values$df_data[-input$Delete, ]
values$df_data <- temp
})
output$df_data_files <- renderPrint(input$file$name)
output$values <- renderPrint(names(values))
output$rvTL <- renderPrint(names(reactiveValuesToList(rvTL))[1] )
output$rvTL_out <- renderTable(reactiveValuesToList(rvTL)[[1]])
output$df_data_out <- renderTable(values$df_data)
})
))
It really is as straightforward as you thought. You were close too, just fell into some syntax traps. I made the following changes:
that c(isolate(.. call was messing things up, I got rid of it. It was leading to those "Warning: Error in as.data.frame.default: cannot coerce class "c("ReactiveValues", "R6")" to a data.frame" errors.
Also you were reusing the rvTL name too often which is confusing and can lead to conflicts, so I renamed a couple of them.
I also added a loaded file name list (lfnamelist) to keep track of what was loaded. I could have used names(rvTL$dflist) for this but it didn't occur to me at the time - and I also this is a useful example of how to organize related reactive values into one declaration.
And then I added rendered selectInput so you can inspect what is saved in the reactiveValue list.
So here is the adjusted code:
library(shiny)
runApp(shinyApp(
ui=(fluidPage(
titlePanel("amend data frame"),
mainPanel(
fileInput("file", "Upload file", multiple=T),
tabsetPanel(type="tabs",
tabPanel("rvTL tab",
numericInput("Delete", "Delete row:", 1, step = 1),
uiOutput("filesloaded"),
actionButton("Go", "Delete!"),
verbatimTextOutput("df_data_files"),
verbatimTextOutput("values"),
verbatimTextOutput("rvTL_names"),
tableOutput("rvTL_out")
),
tabPanel("values tab",
tableOutput("df_data_out")
)
)))),
server = (function(input, output) {
values <- reactiveValues(df_data = NULL) ##reactiveValues
rvTL <- reactiveValues(dflist=NULL,lfnamelist=NULL)
observeEvent(input$file, {
req(input$file)
values$df_data <- read.csv(input$file$datapath)
rvTL$dflist[[input$file$name]] <-read.csv(input$file$datapath)
rvTL$lfnamelist <- c( rvTL$lfnamelist, input$file$name )
})
observeEvent(input$Go, {
temp <- values$df_data[-input$Delete, ]
values$df_data <- temp
})
output$df_data_files <- renderPrint(input$file$name)
output$values <- renderPrint(names(values))
output$rvTL_names <- renderPrint(names(rvTL$dflist))
output$rvTL_out <- renderTable(rvTL$dflist[[input$lftoshow]])
output$df_data_out <- renderTable(values$df_data)
output$filesloaded <- renderUI(selectInput("lftoshow","File to show",choices=rvTL$lfnamelist))
})
))
And here is a screen shot: