Make an eventReactive execute within a Shiny module - r

I have a selectInput UI object and I would like, once that is used to select an entry from the drop-down choices, to read an RDS file. The selectInput's choices are paths to different RDS files. The UI module works fine but the server one doesn't. I get input$study and hence input$dataset1, and then once I select an entry from input$datasets1 the app should start reading the RDS file but it doesn't.
How do I trigger the eventReactive expression inside the module to run and then make that RDS file available to the whole app for other modules to use?
load_sce <- function(input, output, session) {
output$sce_objects <- renderUI({
validate(need(input$study, message = FALSE))
withProgress(message = "Getting SCE objects...", {
objects <- FIND SOME FILES
ns <- session$ns
selectInput(inputId = ns("dataset1"),
label = "Select a specifc analysis",
width = "100%",
choices = c("", objects),
selected = "")
})
})
sce1 <- eventReactive(input$dataset1, {
validate(need(input$dataset1, message = FALSE))
withProgress(message = "Reading data...", { readRDS(input$dataset1) })
})
return( reactive({ sce1 }) )
}

I would review the documentation for withProgress and Progress. withProgress is for tasks operating inside of a loop. https://shiny.rstudio.com/reference/shiny/1.2.0/Progress.html
Also, see this example of a module: https://shiny.rstudio.com/articles/modules.html. In order for the dataframe to be returned as a reactive value outside the module, it should be created as a reactive object inside the module and then returned as such. Also, because input$dataset1 is the only reactive value that sce1 is dependent upon, reactive can be used instead of eventReactive. eventReactive is better suited for inputs such as buttons that are not actually used within the reactive expression, but simply server as the trigger for the expression to execute.
load_sce <- function(input, output, session) {
output$sce_objects <- renderUI({
validate(need(input$study, message = FALSE))
objects <- FIND SOME FILES
ns <- session$ns
selectInput(inputId = ns("dataset1"),
label = "Select a specifc analysis",
width = "100%",
choices = c("", objects),
selected = "")
})
sce1 <- reactive({
validate(need(input$dataset1, message = FALSE))
progress <- Progress$new(session, min=0, max=1)
on.exit(progress$close())
progress$set(message = 'Reading data...')
dataset1 <- readRDS(input$dataset1)
progress$set(value = 1)
return(df)
})
return(sce1)
}

SOLVED
I used the following in the module function:
sce1 <- reactive({
validate(need(input$dataset1, message = FALSE))
withProgress(message = "Reading data...", {
dataset1 <- readRDS(input$dataset1)
}) # withProgress
return(dataset1)
}) # reactive
return(sce1)
and called the module in the main app using:
sce1 <- callModule(load_sce, "load_sce_explore")
Now I can pass sce1 to other modules as a function argument (use sce1 not sce1()) or use it in other pieces of code in the main app (but in this case use sce1()).
Thanks

Related

Hide and Show Download Button Using Shiny Modularity

So far I made a Shiny app that has three inputs connected to the database and a final download button. Everything works well except the download button. The actual data downloading part works but I want to add one last logic that hides the download button if myvars$input3 is empty:
observe({
if (is.null(myvars$var3)) {shinyjs::hide("???")}
else {shinyjs::show("???")}
})
server_tab2.R:
Function1 dropdownTab2Server:
Defined the date range logic with id daterange_tab2
Defined the last input dropdown logic with id var_list_tab2
Function2 downloadTab2Server:
Defined the logic for download button
server.R: (This part is not working)
Want to only show the download button if the third input (myvars$input3) is not empty
ui_tab2.R: Defined the three inputs explained in ui.R:
var_lab_tab2: A static dropdown input with only two choices Choice1 and Choice2
daterange_tab2_ui: A date range
subid_dropdown_tab2_ui: The last dropdown input that depends on the first two
##### server_tab2.R
#### Function 1 - A dropdown input dependent on the date range
dropdownTab2Server <- function(id) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
rv <- reactiveValues()
output$daterange_tab2_ui <- renderUI({
req(input$var_lab_tab2)
dateRangeInput(ns("daterange_tab2"), "Date Range:", start = min_max_date_df$min_date, end = min_max_date_df$max_date) # Retrieved from "global.R"
})
unique_lists_tab2 <- reactive({
sql <- glue_sql("
SELECT
DISTINCT list AS unique_list
FROM table1
WHERE date BETWEEN date ({dateid1_tab2*}) AND date ({dateid2_tab2*})
",
dateid1_tab2 = input$daterange_tab2[1],
dateid2_tab2 = input$daterange_tab2[2],
.con = pool
)
dbGetQuery(pool, sql)
})
output$subid_dropdown_tab2_ui <- renderUI({
req(input$daterange_tab2[1], input$daterange_tab2[2])
shinyWidgets::pickerInput(
ns("var_list_tab2"),
"Stations:",
choices = unique_lists_tab2(),
multiple = T
)
})
observe({
rv$var1 <- input$daterange_tab2[1]
rv$var2 <- input$daterange_tab2[2]
rv$var3 <- input$var_list_tab2
})
return(rv)
}
)
}
#### Function 2 - download button
downloadTab2Server <- function(id, df, filename) {
moduleServer(id, function(input, output, session) {
output$downloadbttn_tab2 <- downloadHandler(
filename = function() {
paste0(filename, ".xlsx")
},
content = function(file) {
WriteXLS::WriteXLS(df, file)
}
)
}
)
}
##### server.R => Struggling with this part
function(input, output, session) {
dropdownTab2Server("dropdown_ui_tab2")
myvars <- dropdownTab2Server("dropdown_ui_tab2")
### download button layout => Struggling with this part
observe({
if (is.null(myvars$var3)) {shinyjs::hide("???")}
else {shinyjs::show("???")}
})
downloadTab2Server(
id = "download_ui_tab2",
df = fake_data(), # reactive
filename = "data"
)
}
##### ui_tab2.R
downloadTab2UI <- function(id) {
ns <- NS(id)
tagList(
shinyWidgets::pickerInput(
ns("var_lab_tab2"),
"ID:",
choices = c("Choice1", "Choice2"), multiple = T
),
uiOutput(ns("daterange_tab2_ui")),
uiOutput(ns("subid_dropdown_tab2_ui")),
downloadButton(ns("downloadbttn_tab2"), "Download Data")
)
}
##### ui.R
downloadTab2UI("download_ui_tab2")
You could the following in the main server part (I've changed it to an observeEvent because I think it's easier to reason what exactly it listens to):
observeEvent(myvars$var3, {
if (is.null(myvars$var3)) {shinyjs::hide("download_ui_tab2-downloadbttn_tab2")}
else {shinyjs::show("download_ui_tab2-downloadbttn_tab2")}
}, ignoreNULL = FALSE)
You need to prefix the download button id with the correct namespace, in your case "download_ui_tab2".
However, this is not great style as you need to manually handle the namespace. A cleaner solution would be to pass myvars to the downloadTab2Server module as an argument and then have the observeEvent in the module code. Then you can directly use downloadbttn_tab2 and don't need to manually prefix the namespace.

Reactives Evaluation in a Loop

I am trying to create a number of tabs based on some list and create similar fields in those tabs with their own identity. Can we create and use reactives like
eval(paste0("Updated_files_list_",Some_List[k], "()"))
I have used following code and one location but that will be different for different tabs it seems that reactives are created for the lists but I am unable to either assign values to it or call it -
Some_List <- list("AGG", "CMP1", "CMP2")
Some_Long_List <- list("Aggregated Things", "Component 1", "Component 2")
sidebar <- dashboardSidebar(
do.call(sidebarMenu, c(lapply(1:length(Some_List), function(i) {
menuItem(Some_Long_List[i], tabName = paste0(Some_List[i],"_tab"))
}))))
uibody <- dashboardBody(
do.call(tabItems, c(lapply(1:length(Some_List), function(i) {
tabItem(tabName = paste0(Some_List[i],"_tab"), h2(Some_Long_List[i]),
fluidPage(fluidRow(column(3,
dateInput(paste0("ME_DATE_output_",Some_List[i]),label=h2("Select a Date"), value="2020-05-29"))
,column(9,column(verbatimTextOutput(paste0("file_path_",Some_List[i])),width=12),hr(),
selectInput(paste0('select_file_',Some_List[i]),'Select a File to display', choices=NULL),hr(),
column(dataTableOutput(paste0('selected_table_',Some_List[i])),width=12)))))
}))))
ui = dashboardPage(dashboardHeader(title = "Results"), sidebar, uibody)
server = function(input, output, session) {
# Location for Source Inputs
Some_loc <- "K:/Outputs"
lapply (1:length(Some_List), function(k){
ME_DATE_GUI <- reactive({input[[paste0("ME_DATE_output_",Some_List[k])]]})
# Files Path
output[[paste0("file_path_",Some_List[k])]] <- renderPrint({ req(Some_loc) })
# Updated Files list
assign(paste0("Updated_files_list_",Some_List[k]), reactive({
lf_some <- list.files(Some_loc,full.names = TRUE)
names(lf_some) <- basename(lf_some)
lf_some
}))
# Fetch selection of file
observeEvent(eval(paste0("Updated_files_list_",Some_List[k], "()")), {
updateSelectInput(session, paste0("select_file_",Some_List[k]), choices=eval(paste0("Updated_files_list_", Some_List[k],"()")))
})
# Display Selected file
output[[paste0("selected_table_",Some_List[k])]] <- renderDataTable({
req(input[[paste0("select_file_", Some_List[k])]])
read.csv(input[[paste0("select_file_", Some_List[k])]], header=T)
})
})
}
shinyApp(ui, server)
In the selectInput field, I can see Update_files_list_AGG(), ("AGG", "CMP1" and "CMP2" in respective tabs) but not the list of files in the assigned location. The error I get is Error: cannot open the connection

Prevent to read file multiple times from dynamic fileInput

I've created a dynamic fileInput in shiny using lapply. When I want to read the file, I've also used lapply in an observer.
The problem of using lapply here is, it is triggered every time I upload a new file and thus, reads all files again and again if a new file is uploaded.
Here I provide a Hello World app. The lapply function depends on an input paramter which I abtracted from for simplicity.
library(shiny)
ui <- fluidPage(
titlePanel("Hello World"),
sidebarLayout(
sidebarPanel(),
mainPanel(
lapply(1:2, function(i) {
fileInput(
paste0("file", i),
label = NULL,
multiple = F,
accept = c(
"text/csv",
"text/comma-separated-values,text/plain",
".csv"
),
buttonLabel = paste("File", i)
)
}),
verbatimTextOutput("list")
)
)
)
server <- function(input, output) {
r <- reactiveValues()
observe({
lapply(1:2, function(i) {
file <- input[[paste0("file",i)]]
if(is.null(file)) return()
isolate({
r$file[[paste(i)]] <- readr::read_csv2(file = file$datapath)
})
})
})
output$list <- renderPrint(reactiveValuesToList(r))
}
shinyApp(ui = ui, server = server)
How to replace the loop or add a requirement to lapply?
While I started down the road of cache-invalidation in the comments, I think something else may work better for you since you have a fixed number of fileInput fields: swap the lapply and observe lines in your code (plus a couple of other tweaks).
server <- function(input, output) {
lapply(paste0("file", 1:2), function(nm) {
observeEvent(input[[ nm ]], {
req(input[[nm]], file.exists(input[[nm]]$datapath))
readr::read_csv2(file = input[[nm]]$datapath)
})
})
}
Explanation:
I'm creating a list of reactive blocks instead of a reactive block operating on a list. This means "file1" won't react to "file2".
I short-cutted the definition of the input names by putting paste0(...) in the data of the lapply instead of in the function, though it'd be just as easy to do
lapply(1:2, function(i) {
nm <- paste0("file", i)
# ...
})
It's important to have nm defined outside of the observeEvent, and it has to do with delayed evaluation and namespace search order. I fell prey to this a few years ago and was straightened out by Joe Cheng: you can't use a for loop, it must be some environment-preserving operation like this.
N.B.: this is a stub of code, and it is far from complete: having an observe or observeEvent read the data and then discard it is wrong ... it's missing something. Ideally, this should really be a reactive or eventReactive block, or the processed data should be stored in a reactiveValues or reactiveVal. For example:
server <- function(input, output) {
mydata <- lapply(paste0("file", 1:2), function(nm) {
observeEvent(input[[ nm ]], {
req(input[[nm]], file.exists(input[[nm]]$datapath))
readr::read_csv2(file = input[[nm]]$datapath)
})
})
observe({
# the following are identical, the latter more declarative
mydata[[1]]
mydata[["file1"]]
})
}
(And another note about defensive programming: you cannot control perfectly how readr::read_csv2 reacts to that file ... it may error out for some reason. One further step would be to wrap it in tryCatch(..., error = function(e) { errfun(e); NULL; }) where errfun(e) does something meaningful with the error message (logs it and/or gives it to the user in a modal popup) and then returns NULL so that reactive blocks downstream can use req(mydata[[1]]) and will not try to process the NULL.
server <- function(input, output) {
mydata <- lapply(paste0("file", 1:2), function(nm) {
observeEvent(input[[ nm ]], {
req(input[[nm]])
file <- input[[nm]]
tryCatch(
readr::read_csv2(file = input[[nm]]$datapath),
error = function(e) { errfun(e); NULL; })
})
})
observe({
# the following are identical, the latter more declarative
mydata[[1]]
mydata[["file1"]]
})
}

How to correctly use a checkboxInput 'All/None' in a uiOutput context in R Shiny?

Here is the context :
library(shiny)
liste_statut <- c("A","B","C")
ui <- shinyUI(fluidPage(uiOutput("testUI")))
server <- function(input, output, session) {
output$testUI <- renderUI({
navbarPage(
title = "Test",
tabPanel(icon = icon("users"), 'Test',
sidebarPanel(
# Statut
checkboxGroupInput("statut", "Statut", liste_statut, liste_statut),
checkboxInput('selectall_statut', 'Tout / Aucun', T))))
})
# observe({
# updateCheckboxGroupInput(
# session, 'statut', choices = liste_statut,
# selected = if (input$selectall_statut) liste_statut
# )
# })
}
shinyApp(ui = ui, server = server)
I would like to use my checkbox All/None (in comment lines) properly cause in this case i have a "Warning: Error in if: argument is of length zero". Where should i put it or maybe should i redefine properly something in the UI part?
I willingly use the renderUI/uiOutput option (contrary to the "standard mode" ui/server) because in future, i will add an authentification module, so be able to display several "panels" according to user.
Thanks and sorry for my terrible english :).
The following works for me:
library(shiny)
liste_statut <- c("A","B","C")
ui <- shinyUI(fluidPage(uiOutput("testUI")))
server <- function(input, output, session) {
output$testUI <- renderUI({
navbarPage(
title = "Test",
tabPanel(icon = icon("users"), 'Test',
sidebarPanel(
# Statut
checkboxGroupInput("statut", "Statut", liste_statut, liste_statut),
checkboxInput('selectall_statut', 'Tout / Aucun', T))))
})
observeEvent(input$selectall_statut,{
val <- liste_statut
if(!input$selectall_statut)
val <- character(0)
updateCheckboxGroupInput(
session, 'statut',
selected = val
)
})
}
I initially tried selected = ifelse(input$selectall_statut, liste_statut, character(0)) instead of the intermediate variable val. However, ifelse() only returned a single value, not a vector.
If you are going to do this many times over, then I would recommend a custom ifelse function. Perhaps something like the following:
ifelse2 <- function(test, yes, no){
if(test)
return(yes)
return(no)
}

Run R script after input in Shiny

Good morning everyone,
I have a Shiny application that collects 5 inputs from the users and stores them into variables.
Then, I would be able to use another R script that would run based on the information provided by the user.
Here is a sample of my Shiny App :
jscode <- "shinyjs.closeWindow = function() { window.close(); }"
#Define UI for application
ui <- pageWithSidebar(
#App title
headerPanel("Filters applied for Powerpoints"),
#Panel to display the filters
sidebarPanel(
#Select dates
dateInput(inputId = "startDate", label = "Start date : ", value = "2018-12-01", format = "yyyy/mm/dd"),
dateInput(inputId = "endDate", label = "End date : ", value = "2018-12-31", format = "yyyy/mm/dd"),
#Select brand template
selectInput("Brand", label = "Select brand : ", choices = list("Carat" = "Carat", "Amplifi" = "Amplifi", "iProspect" = "iProspect", "Isobar" = "Isobar")),
#Select medium type
selectInput("Medium", label = "Select medium type : ", choices = list("Social Post" = "Social Post", "Display" = "Display", "Programmatic" = "Programmatic", "SEA" = "SEA")),
#Enter the plan ID of your campaign
textInput("Camp", label = "Enter the plan ID of your campaign : ", value = ""),
#Button to close the window, then run script
useShinyjs(),
extendShinyjs(text = jscode, functions = c("closeWindow")),
actionButton("close", "Close and run")
),
mainPanel()
)
#Define server logic
server <- function(input, output, session){
observe({
startDate <<- input$startDate
endDate <<- input$endDate
brand <<- input$Brand
medium <<- input$Medium
campaign <<- input$Camp
})
observeEvent(input$close, {
js$closeWindow()
stopApp()
})
source("C:/Users/RPeete01/Desktop/Automated powerpoints/Datorama R/Datorama reporting R/DatoramaSocial.R")
}
#Run the application
shinyApp(ui = ui, server = server)
I've used the source function but it doesn't work.
If someone has an idea, please let me know.
Thanks a lot,
RĂ©mi
You should take advantage of built in onStop functions in shiny to execute some functions before the stopApp() call
library(shiny)
if (interactive()) {
# Open this application in multiple browsers, then close the browsers.
shinyApp(
ui = basicPage("onStop demo",actionButton("close", "Close and run")),
server = function(input, output, session) {
onStop(function() cat("Session stopped\n"))
observeEvent(input$close, {
stopApp()
})
},
onStart = function() {
cat("Doing application setup\n")
onStop(function() {
cat("Doing application cleanup, your functions go here\n")
})
}
)
}
Instead of creating a function to replace your script, you can source your script by supplying an environment to the local option. This environment must contain the objects needed by your script. Something like that:
mylist <- reactiveVal() # we will store the inputs in a reactive list
observe({ # create the list
mylist(list(
startDate = input$startDate,
endDate = input$endDate,
brand = input$Brand,
medium = input$Medium,
campaign = input$Camp))
})
observeEvent(input$runScript, { # "runScript" is an action button
source("myscript.R", local = list2env(mylist()))
})
EDIT
Here is a full example.
library(shiny)
ui <- fluidPage(
textInput("text", "Enter text", value = "test"),
actionButton("runScript", "Run")
)
server <- function(input, output, session) {
mylist <- reactiveVal() # we will store the inputs in a reactive list
observe({ # create the list
mylist(list(
text = input$text))
})
observeEvent(input$runScript, { # "runScript" is an action button
source("myscript.R", local = list2env(mylist()))
})
}
shinyApp(ui, server)
File myscript.R:
writeLines(text, "output.txt")
When I run the app and click on the button, the file output.txt is correctly created (i.e. the script is correctly sourced).
Your script DatoramaSocial.R should be formulated as a function that takes your 5 input values as arguments. As to the return value, well you haven't told us what you want to do with it. By formulating it as a function I mean wrap everything in DatoramaSocial.R in a function (or several subfunctions). The code for that function can easily reside in the external script file or be pasted before the ui and server statements in your shiny app. If the former, simply include the definitions by calling source('DatoramaSocial.R') before your ui and server statements.
Now, in your server function, you can simply call it as a reaction to changes in the input:
observe({
DatoramaSocial(input$startDate, input$endDate, input$Brand, input$Medium, input$Camp)
})
Although in this case, I recommend inserting an actionbuttonInput and having the user click that when they have selected all their inputs. In which case, update to:
observeEvent(input$actionbutton, ignoreInit=TRUE, {
DatoramaSocial(input$startDate, input$endDate, input$Brand, input$Medium, input$Camp)
})
where actionbutton is the actionbutton's inputId.

Resources