I would like to know how it is possible with callModule() to hide html if my data are imported with a reactive boolean (boolmark)
Here's my code
ui.R
shinyUI(
column(width=9,
div( style = "width:100% ; max-width: 1200px; height: 100%" ,
conditionalPanel(condition = 'output.boolmark', tags$(HTML code),
conditionalPanel(condition = '!output.boolmark', code))
div(id="pass",style = "word-wrap: break-word;",
column(width = 3,
csvFileInput("datafile", "User data (.csv format)"))
server.R
shinyServer(
csvf <- callModule(csvFile, "datafile",stringsAsFactors = FALSE))
csvmodules.R
csvFileInput <- function(id, label = "CSV file") {
ns <- NS(id)
tagList(
fileInput(ns("file"),multiple = T, label),
checkboxInput(ns("heading"), "Has heading"),
selectInput(
ns("quote"),
"Quote",
c(
"None" = "",
"Double quote" = "\"",
"Single quote" = "'"
)))}
csvFile <- function(input, output, session, stringsAsFactors) {
showmark <<- T # Boolean uses to hide or show the mardkwon serving to load data
userFile <- reactive({
# If no file is selected, don't do anything
validate(need(input$file, message = FALSE))})
output$boolmark <- reactive({
showmark
})
observe({
print(showmark)})
outputOptions(output,"boolmark",suspendWhenHidden=F)
csv <- lapply(
csvtest,
FUN = function (x)
fread(data.table = F,check.names = F,
header = T,sep = ";",dec = ","
)
)
observe({
showmark <<-F
print(showmark)
}) # modify and lock the bool value to false
output$boolmark <- reactive({
showmark
})
return (csv)}
)}
Well, this code is working outside the module but the html is not hidden when using the modules
Why and how to fix it ?
As already mentioned in the comments the problem is that conditionalPanel needs to know in which namespace to look for the input- and output ids appearing in the condition argument. This can be done by passing the namespace function to conditionalPanel.
ns <- NS("datafile")
conditionalPanel("!output.boolmark", ns, ...)
A simplified and fixed version of your app could look like this:
library(shiny)
myModuleUI <- function(id) {
ns <- NS(id)
checkboxInput(ns("checkbox"), "checkbox")
}
myModule <- function(input, output, session) {
output$value <- reactive({ input$checkbox })
outputOptions(output, "value", suspendWhenHidden = FALSE)
}
shinyApp(
fluidPage(
myModuleUI("myModule"),
conditionalPanel("output.value", ns = NS("myModule"), "checkbox is TRUE")
),
function(input, output, session) {
callModule(myModule, "myModule")
}
)
Normally, it is not a good practice to refer to the module by id from outside the module so I would use the following adaptation.
myModuleUI <- function(id) {
ns <- NS(id)
checkboxInput(ns("checkbox"), "checkbox")
}
myModule <- function(input, output, session) {
return(reactive({input$checkbox}))
}
shinyApp(
fluidPage(
myModuleUI("myModule"),
conditionalPanel("output.value", "checkbox is TRUE")
),
function(input, output, session) {
output$value <- callModule(myModule, "myModule")
outputOptions(output, "value", suspendWhenHidden = FALSE)
}
)
The general idea behind this is that you either want to implement the logic inside your module (call conditionalPanel in myModuleUI) or return a value from myModule that gives the main app the possibility to work with the reactive objects generated by the module.
Related
Let's say I have 4 sets of UI/Server modules in 4 different directories ("./X1/Y1/", "./X1/Y2/", "./X2/Y1/", "./X2/Y2/"). I want to load the selected set based on the input in the sidebar.
I tried using source() within dashboardBody(), but I was not successful.
library(shiny)
library(shinydashboard)
# path to modules
in_path <- "C:/a/b/c/"
# ui
ui <- dashboardPage(
dashboardHeader(title = "test"),
dashboardSidebar(
br(),
selectInput('f1', 'Folder 1', choices = c("X1", "X2")),
helpText(""),
selectInput('f2', 'Folder 2', choices = c("Y1", "Y2")),
br(),
actionButton("load", "Load", icon("thumbs-up"), width = "85%")
),
dashboardBody(
# UI module here from, e.g., "C:/a/b/c/X1/Y2/my_UI.R"
)
)
# server
server <- function(input, output, session) {
# server module here from, e.g., "C:/a/b/c/X1/Y2/my_Server.R"
}
shinyApp(ui, server)
As shiny modules are simply functions, I'd source them in the beginning, and use uiOutput to display the differnt modules.
Here's a working example of the general idea (sample module code proudly stolen from the official Shiny documentation):
<mod1.R>
counterButton <- function(id, label = "Counter") {
ns <- NS(id)
tagList(
actionButton(ns("button"), label = label),
verbatimTextOutput(ns("out"))
)
}
counterServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
count <- reactiveVal(0)
observeEvent(input$button, {
count(count() + 1)
})
output$out <- renderText({
count()
})
count
}
)
}
<mod2.R>
csvFileUI <- function(id, label = "CSV file") {
ns <- NS(id)
tagList(
fileInput(ns("file"), label),
checkboxInput(ns("heading"), "Has heading"),
selectInput(ns("quote"), "Quote", c(
"None" = "",
"Double quote" = "\"",
"Single quote" = "'"
))
)
}
csvFileServer <- function(id, stringsAsFactors = TRUE) {
moduleServer(
id,
## Below is the module function
function(input, output, session) {
# The selected file, if any
userFile <- reactive({
# If no file is selected, don't do anything
validate(need(input$file, message = FALSE))
input$file
})
# The user's data, parsed into a data frame
dataframe <- reactive({
read.csv(userFile()$datapath,
header = input$heading,
quote = input$quote,
stringsAsFactors = stringsAsFactors)
})
# We can run observers in here if we want to
observe({
msg <- sprintf("File %s was uploaded", userFile()$name)
cat(msg, "\n")
})
# Return the reactive that yields the data frame
return(dataframe)
}
)
}
<app.R>
library(shiny)
source("mod1.R")
source("mod2.R")
my_mods <- list("Counter Button" = list(ui = counterButton,
server = counterServer),
"CSV Uploader" = list(ui = csvFileUI ,
server = csvFileServer))
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("mod_sel",
"Which Module should be loaded?",
names(my_mods))
),
mainPanel(
uiOutput("content"),
verbatimTextOutput("out")
)
)
)
server <- function(input, output) {
uuid <- 1
handler <- reactiveVal()
output$content <- renderUI({
my_mods[[req(input$mod_sel)]]$ui(paste0("mod", uuid))
})
observeEvent(input$mod_sel, {
handler(my_mods[[req(input$mod_sel)]]$server(paste0("mod", uuid)))
uuid <<- uuid + 1
})
output$out <- renderPrint(req(handler())())
}
shinyApp(ui, server)
Some Explanation
You put the module code in mod[12].R and it is rather straight forward.
In your main app, you load both(!) source files and for housekeeping reasons, I put both modules functions (ui and server) in a list, but this is not strictly necessary, but facilitates future extension.
In your UI you have an uiOutput which renders dynamically according to the selected module.
In your server you put the code to dynamically render the UI and call the respective server function.
The uid construct is basically there to force a fresh render, whenever you change the selection. Otherwise, you may see still some old values whenever you come back to a module which you have rendered already.
I'm creating a shiny module, where I wish to display some pop-up messages to the user via shinyalerts and include dropdown menus via htlm = TRUE and shinyWidgets::pickerInput. For each shinyalert the options should be different and the alerts should appear right after each other when the user has selected the relevant option.
However, when running the shinyalerts within a for loop, only the first alert shows the drop-down, the following does not. Please have a look at the example below and screenshots. Any ideas what I'm doing wrong?
Module UI:
mod_match_columns_ui <- function(id){
ns <- NS(id)
tagList(
shinyalert::useShinyalert(),
actionButton(ns("run"), label = "Start!")
)
}
Module server:
mod_match_columns_server <- function(input, output, session){
ns <- session$ns
options <- list(c("option_1","option_2"),
c("option_3","option_4"))
observeEvent(input$run, {
for(col in 1:2){
nms <- options[[i]]
output[[paste0("dropdown",col)]] <- renderUI({
shinyWidgets::pickerInput(
inputId = ns(paste0("options",col)),
label = "Options listed below",
choices = nms,
selected = "",
multiple = FALSE,
options = shinyWidgets::pickerOptions(size = 15)
)
})
shinyalert::shinyalert(
title = "Pick an option!",
html = TRUE,
text = tagList(
uiOutput(ns(paste0("dropdown", col)))
),
inputId = ns(paste0("modal", col))
)
}
})
}
Run module:
library(shiny)
ui <- fluidPage(
mod_match_columns_ui("match_columns_ui_1")
)
server <- function(input, output, session) {
callModule(mod_match_columns_server, "match_columns_ui_1")
}
shinyApp(ui = ui, server = server)
First iteration:
Second iteration:
Why is the dropdown not shown in the second iteration?? Thanks
Try this
library(shiny)
library(shinyalert)
mod_match_columns_ui <- function(id){
ns <- NS(id)
tagList(
shinyalert::useShinyalert(),
actionButton(ns("run"), label = "Start!")
)
}
mod_match_columns_server <- function(id) {
moduleServer(id,
function(input, output, session) {
ns <- session$ns
options <- list(c("option_1","option_2"),
c("option_3","option_4"))
lapply(1:2, function(col){
output[[paste0("dropdown",col)]] <- renderUI({
shinyWidgets::pickerInput(
inputId = ns(paste0("options",col)),
label = paste("Options",col,"listed below"),
choices = options[[col]],
selected = "",
multiple = FALSE,
options = shinyWidgets::pickerOptions(size = 15)
)
})
})
observeEvent(input$run, {
shinyalert::shinyalert(
title = "Pick an option!",
html = TRUE,
text = tagList(
lapply(1:2, function(i){uiOutput(ns(paste0("dropdown",i)))})
)
# callbackR = function(x) { message("Hello ", x) },
# inputId = ns(paste0("modal"))
)
})
observe({
print(input$options1)
print(input$options2)
print(input$shinyalert)
})
})
}
ui <- fluidPage(
tagList(
mod_match_columns_ui("match_columns_ui_1")
)
)
server <- function(input, output, session) {
mod_match_columns_server("match_columns_ui_1")
}
shinyApp(ui = ui, server = server)
I'm trying to create a tabset where tabs are dynamically added. Each new tab has the same carousel with images. The carousel is loaded from a module.
This would be the desired end result, but that works for multiple dynamically added tabs:
Reading other SO questions leads me to believe that I might need a nested module. Alternatively I've made a mistake with insertUI. Help much appreciated!
Here is a MVE where you need to place a single png in the same folder as the code:
library(shiny)
library(slickR)
my_module_UI <- function(id) {
ns <- NS(id)
slickROutput(ns("slickr"), width="100%")
}
my_module <- function(input, output, session) {
output$slickr <- renderSlickR({
imgs <- list.files("", pattern=".png", full.names = TRUE)
slickR(imgs)
})
}
ui <- fluidPage(
tabItem(tabName = "main_tab_id",
tabsetPanel(id = "test_tabs",
tabPanel(
title = "First tab",
value = "page1",
fluidRow(textInput('new_tab_name', 'New tab name'),
actionButton('add_tab_button','Add'))
)
)
)
)
server <- function(input, output, session) {
tab_list <- NULL
observeEvent(input$add_tab_button,
{
tab_title <- input$new_tab_name
if(tab_title %in% tab_list == FALSE){
appendTab(inputId = "test_tabs",
tabPanel(
title=tab_title,
div(id="placeholder") # Content
)
)
# A "unique" id based on the system time
new_id <- gsub("\\.", "", format(Sys.time(), "%H%M%OS3"))
insertUI(
selector = "#placeholder",
where = "beforeBegin",
ui = my_module_UI(new_id)
)
callModule(my_module, new_id)
tab_list <<- c(tab_list, tab_title)
}
updateTabsetPanel(session, "test_tabs", selected = tab_title)
})
}
shinyApp(ui, server)
This is an interesting exercise in modules.
carousel_module simply renders the carousel
my_tab module, creates a tab and an observeEvent for each tab which listens to tab clicks
library(shiny)
library(slickR)
carousel_ui <- function(id){
ns <- NS(id)
slickROutput(ns("slickr"), width="100%")
}
carousel_module <- function(input, output, session) {
output$slickr <- renderSlickR({
imgs <- list.files("~/Desktop/imgs", pattern=".png", full.names = TRUE)
slickR(imgs)
})
}
my_tab <- function(input,output,session,parent_session,tab_element,tab_name){
ns = session$ns
appendTab(inputId = "test_tabs",
tabPanel(
title = tab_name,
value = tab_name,
carousel_ui(ns("carousel")) # Operating in the parent session so explicitly supply the namespace
),
session = parent_session
)
updateTabsetPanel(parent_session, "test_tabs", selected = tab_name) # Refer to test_tabs from the parent namespace
# Need to update the carousel every time the user clicks on a tab
# Else the carousel is only updated on the latest tab created
observeEvent(tab_element(),{
req(tab_element())
if(tab_element() == tab_name){
cat("Running\n")
callModule(carousel_module,"carousel")# This module knows the namespace so no need to supply the namespace
}
})
}
ui <- fluidPage(
tabsetPanel(id = "test_tabs",
tabPanel(
title = "First tab",
value = "page1",
fluidRow(textInput('new_tab_name', 'New tab name'),
actionButton('add_tab_button','Add'))
)
)
)
)
server <- function(input, output, session) {
tab_list <- NULL
observeEvent(input$add_tab_button,{
tab_title <- input$new_tab_name
callModule(my_tab,tab_title,session,reactive(input$test_tabs),input$new_tab_name)
})
}
shinyApp(ui, server)
Can you provide a MWE of modularized shiny code that uses renderUI? I'd like an example to follow.
There is an excellent tutorial that discusses this here: https://shiny.rstudio.com/articles/modules.html however, it doesn't show how to integrate the modularization of renderUI components in the ui nor in the server.
Here's what I've tried so far:
In my ui code, I had:
htmlOutput("selectionUI")
In my server code, I had:
output$selectionUI <- renderUI({
req(input$Filter)
selectInput(
inputId = "Selection",
label = "Selection",
choices = get("qlist", envir = get(input$source))[[input$Filter]]$responses)
})
Now I would like to modularize this becuase it's a sometimes repeated element, but I'm not sure how to actually insert it into my ui/server code once I'm done.
Here's what I've tried:
selectionChooserUI <- function(id) {
ns <- NS(id)
uiOutput(ns('controls'))
}
selectionChooser <- function(input, output, session, data, sourcedata, filter) {
output$selectionUI <- renderUI({
req(input$Filter)
ns <- session$ns
selectInput(
inputId = ns('Selection'),
label = 'Selection',
choices = get('qlist', envir = get(input[[sourcedata()]]))[[input[[filter()]]]]$responses
)
})
}
What do I have to put into my ui code to get it to diplay, currently I'm getting complaints that "output" is missing with no default?
I'm calling it presently in my ui code, using:
selectionChooserUI("selection")
It's probably something like this. I haven't test it out since I don't have your data ..
library(shiny)
ui <- fluidPage(
h1("Get me a Module!"),
selectInput("source", "Some source", choices = letters[1:4]),
selectInput("filter", "Some filter", choices = letters[1:4]),
selectionChooserUI("id_of_me")
)
server <- function(input, output, session) {
get_me_choices <- reactive({
get("qlist", envir = get(req(input$source)))[[req(input$filter)]]$responses })
callModule(module = selectionChooser, id = "id_of_me", choices = get_me_choices)
}
selectionChooserUI <- function(id) {
ns <- NS(id)
uiOutput(ns('selection'))
}
selectionChooser <- function(input, output, session, choices) {
ns <- session$ns
output$selection <- renderUI({
selectInput(
inputId = ns('selection'),
label = 'Selection',
choices = choices
)
})
}
I have created a sample app below to illustrate the issue I am having. I have an application in Shiny that is using many layers of modules. I am very familiar with using modules and returning reactive values from the modules themselves. However when I need to use lapply to create multiple calls of modules (in this case slider_menu_item_shiny function to create multiple sliders), each which return the reactive value that is set by the user in sliders, I am not sure how to dynamically capture all of the output reactive variables into one reactive vector.
Right now I have 2 sliders hard coded in and this simple app works. However I want to be able to type in an arbitrary value in the first input, have the app create that amount of slider modules using the lapply statement (for the callModule(slider_menu_item_shiny) call too) and then have slider_value_vector contain a vector of that length with all of the slider values.
I feel like I am missing a fundamental trick to making this work. I would really appreciate the learning experience and all of the help.
ui.R code
library(shiny)
library(shinydashboard)
library(DT)
#### MODULE CODE ####
source("modules.R")
# define header
header <- dashboardHeader(
title = "Test"
)
# define body
body <- dashboardBody(
tabItems(
body_set_shinyUI(id = "body_test_mod", tab_name = "body_test_mod")
)
)
# define sidebar
sidebar <- dashboardSidebar(
sidebarMenu(id = "dashboard_menu",
menuItem("Test Body", tabName = "body_test_mod")
)
)
dashboardPage(skin = "blue",
header,
sidebar,
body
)
server.R code
library(shiny)
library(shinydashboard)
library(DT)
#### MODULE CODE ####
source("modules.R")
#### SERVER CODE ####
function(input, output, session) {
callModule(body_set_shiny, id = "body_test_mod")
}
modules.R code
### body_set_shiny
body_set_shinyUI <- function(id, tab_name) {
ns <- NS(id)
tabItem(tabName = tab_name,
fluidRow(
column(12,
inner_body_test_menu_shinyUI(ns("inner_body_test_mod"))
)
)
)
}
body_set_shiny <- function(input, output, session) {
callModule(inner_body_test_menu_shiny, id = "inner_body_test_mod")
}
### inner_body_test_menu_shiny
inner_body_test_menu_shinyUI <- function(id) {
ns <- NS(id)
fluidRow(
column(12,
box(title = "Test Inner Menu",
width = 12,
fluidRow(
column(12,
wellPanel(
uiOutput(ns("inner_number_menu")),
uiOutput(ns("inner_sliders_menu")),
uiOutput(ns("inner_text_output"))
)
)
)
)
)
)
}
inner_body_test_menu_shiny <- function(input, output, session) {
output$inner_number_menu <- renderUI({
ns <- session$ns
textInput(ns("inner_number_value"), label = "Enter Number of Sliders", value = "2")
})
slider_length <- reactive({
if (is.null(input$inner_number_value))
return()
as.numeric(input$inner_number_value)
})
output$inner_sliders_menu <- renderUI({
if (is.null(slider_length()))
return()
ns <- session$ns
lapply((1:slider_length()), function(m) {
slider_menu_item_shinyUI(ns(paste("slider_menu_item_", m, sep = "")))
})
})
output$inner_text_output <- renderText({
if (is.null(slider_value_vector()))
return()
paste("You have entered", slider_value_vector())
})
slider_value_vector <- reactive({
if (is.null(slider_length()))
return()
c(as.numeric(slider_v1()[[1]]),as.numeric(slider_v2()[[1]]))
})
slider_v1 <- callModule(slider_menu_item_shiny, paste("slider_menu_item_", 1, sep = ""))
slider_v2 <- callModule(slider_menu_item_shiny, paste("slider_menu_item_", 2, sep = ""))
}
slider_menu_item_shinyUI <- function(id) {
ns <- NS(id)
uiOutput(ns('sider_output_menu'))
}
slider_menu_item_shiny <- function(input, output, session, slider_value = 0, slider_name = "No Name Found") {
output$sider_output_menu <- renderUI({
ns <- session$ns
uiOutput(ns("slider_item_menu"))
})
output$slider_item_menu <- renderUI({
ns <- session$ns
sliderInput(ns("slider_item"), label = "Slider Example", min = -1, max = 1, value = 0.5, step = 0.01)
})
return(reactive(list(input$slider_item)))
}