Dynamic UI/Server Modules in Shiny Dashboard Based on Inputs in UI - r

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.

Related

Problems using R shiny modules

I have just gotten into modules and it seems like I have not figured it out yet. The following code does not run properly. The app closes immediately after it opens.
It seems like the id does not match. However, I am not quite sure what goes wrong.
I set up input and output for the UI in the module and then set up the server for the module. I then combine everything in the UI and Server for the app and run it.
SharePrepayInputUI<- function(id, vChoice){
ns <- NS(id)
tagList(
fluidRow(
column(2,
pickerInput(ns("navn"),"Select Segment", choices=vChoice, options = list(
"actions-box" = TRUE,
"live-search" = TRUE),
multiple = TRUE))
)
)
}
ShareOutpuitUI <- function(id){
ns <- NS(id)
tagList(
fluidRow(
column(6,
dataTableOutput(ns("cls"))),
column(4,
dataTableOutput(ns("dbl")))
)
)
}
ShareServer <- function(id){
moduleServer(id, function(input, output, session){
# Table for cash loan share
output$cls <- renderDataTable({
DT::datatable(mCls[mCls$Segment %in% input$navn,], container = sketch_cls, rownames = FALSE)
})
output$dbl <- renderDataTable({
DT:datatable(mDbl[mDbl$Segment %in% input$navn,], container = sketch_dbl, rownmaes = FALSE)
})
})
}
UdtrDebUI <- fluidPage(
SharePrepayInputUI(id = "module_1", vChoice = mCls$Segment),
ShareOutpuitUI(id = "module_1")
)
UdtrDebServer <- function(id){
ShareServer(id = "module_1")
}
shinyApp(UdtrDebUI, UdtrDebServer)

Unable to access the value of radioButton when created inside a shiny server module

My shinyapp is build using modules, the radioBox component inputId = modelling_type is created in the server, using a renderUI function and stored under outputId = modelling_type_ui
As I'm using modules, I have name spaced my IDs in the mod_ui, and then in order to (attempt!) to use the same name space function in the mod_server I have called it via ns <- parentsession$ns. This doesn't throw an error. But I would now expect to access the value of the RadioBox via input$modelling_type
This isn't working! So I must be calling the value incorrectly.
Here is the code:
library(shiny)
library(shinyalert)
library(shinydashboard)
library(shinyjs)
library(tidyverse)
# modules ------------------------------------------
mod_ui <- function(id){
ns <- NS(id)
fluidPage(
uiOutput(outputId = ns("modelling_type_ui")),
textOutput(outputId = ns("capture"))
)
}
mod_server <- function(id, parentsession){
moduleServer(id,
function(input, output, server){
ns <- parentsession$ns
output$modelling_type_ui = renderUI({
print(input$modelling_type) # this should not be null
radioButtons(
inputId = ns("modelling_type"),
label = "Choose a modelling technique",
choices = c("OLS",
"Bayesian"),
selected = "OLS")
})
output$capture = renderText({ paste0("modelling type selected:", input$modelling_type) })
})
}
# call app ---------------------------------------
# run app
ui <- function(){ mod_ui("mt") }
server <- function(input, output, session){ mod_server("mt", session) }
shinyApp(ui = ui, server = server)
Any help appreciated. Usually I would just call radioButtons in the UI, and use updateradioButtons function in the server, but I'm dealing with a legacy app which uses the below method repeatedly.
To expand on my comment above, here is a MWE that I believe does what you want.
I'm not sure why you're using uiOutput and renderUI. I assume it's needed in your actual use case, but it's not needed here. Also, there's no need to muck about with parentsession and the like.
One reason why your debug print prints NULL is that you haven't defined the radio group at the time you try to print its value.
library(shiny)
library(tidyverse)
mod_ui <- function(id){
ns <- NS(id)
fluidPage(
uiOutput(outputId = ns("modelling_type_ui")),
textOutput(outputId = ns("capture"))
)
}
mod_server <- function(id) {
moduleServer(
id,
function(input, output, session){
ns <- session$ns
output$modelling_type_ui = renderUI({
radioButtons(
inputId = ns("modelling_type"),
label = "Choose a modelling technique",
choices = c("OLS","Bayesian"),
selected = "OLS"
)
})
output$capture <- renderText({
paste0("modelling type selected: ", input$modelling_type)
})
rv <- reactive({
input$modelling_type
})
return(rv)
}
)
}
ui <- function() {
fluidPage(
mod_ui("mt"),
textOutput("returnValue")
)
}
server <- function(input, output, session) {
modValue <- mod_server("mt")
output$returnValue <- renderText({
paste0("The value returned by the module is ", modValue())
})
}
shinyApp(ui = ui, server = server)

RShiny and Modules output. Object of type environment is not subsettable

Dear people of sackoverflow,
I am currently trying to create an app where a user can upload a dataset from various sources. I tried to isolate the code for the upload in a separate Shiny module. However, R throws an error saying that an environment object is not subsettable. I am fairly new to shiny so would appreciate any advice regarding the use of modules too. :)
library(shiny)
fileInputUI <- function(id){
ns <- NS(id)
fileInput(
inputId = ns("file"),
label = "Select a file"
)
}
fileInputServer <- function(id){
moduleServer(id, function(input, output, session){
dataset <- reactive({
req(input$file)
# Get file extension and datapath
ext <- tools::file_ext(input$file$name)
datapath <- input$file$datapath
# Need reactive values to store input data in
read_data_options <- reactiveValues()
read_data_options$sep <- NULL
if(ext == "csv"){
choices_sep <- c(",", ";", "", "\\t")
names(choices_sep) <- c("Comma (,)", "Semicolon (;)", "White space ( )", "Tab separated (\\t)")
showModal(
ui = modalDialog(
selectInput(
inputId = NS(id, "sep"),
label = "Which separator is used for your data",
choices = names(choices_sep),
selected = ""
),
# Input is required so no dismiss button
footer = tagList(
modalButton("Dismiss"),
actionButton(
inputId = NS(id, "submit_csv"),
label = "Submit")
),
easyClose = TRUE
)
)
# Set input value from submit button
observeEvent(
eventExpr = input$submit_csv,
handlerExpr = {
read_data_options$sep <- choices_sep[input$sep]
removeModal()
read.csv(
file = datapath,
sep = read_data_options$sep
)
}
)
} else {
NULL
}
})
dataset
})
}
# Shiny App
ui <- fluidPage(
fileInputUI("test"),
tableOutput("head_data")
)
server <- function(input, output, session) {
dataset <- fileInputServer("test")
output$head_data <- renderTable(head(dataset()))
}
shinyApp(ui, server)
I used modalDialog because I figured it to be the simplest approach to asking users about additional inputs to the read function for example the separator in case of read.csv or the startRow for read.xlsx etc.
Thanks for your replies

lapply modules and make use of reactive return from shiny modules

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)))
}

Shiny modules import data and hide html if success

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.

Resources