I'm trying to build an application, where the user can add a piece of UI (in this case an input text field) by clicking a button. As this is only part of a bigger application, I wish to use modules to keep structure in my project.
However, my text field won't show after clicking the action button. I'm using the new function moduleServer() that was recently introduced by shiny.
Here is a reprex
library(shiny)
ModularizedUI <- function(id) {
ns <- NS(id)
fluidPage(
# Input: Action button to add text field
actionButton(inputId = ns("add_text"),
label = "Add text field"),
)
}
ModularizedServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
ns <- session$ns
# Track the number of input boxes to render for test training
counter_text <- reactiveVal(0)
# Input/Output fields for start dates test training
observeEvent(input$add_text, {
counter_text(counter_text() + 1)
# Add UI if this button is clicked
insertUI(
selector = "#add",
where = "afterEnd",
ui = textInput(inputId = ns(paste0("textfield_", counter_text())),
label = "New text")
)
})
}
)
}
ui <- navbarPage("Dashboard",
tabPanel("Text fields",
ModularizedUI(id = "Text_Fields")
)
)
server <- function(input, output, session) {
ModularizedServer("Text_Fields")
}
shinyApp(ui = ui, server = server)
Any help is appreciated!
You gave insertUI the selector #add, which defines where the new elements have to be inserted. But there is no element with id #add in your UI.
Add div(id = '#add') after your actionButton and it should work.
(I would then use where = beforeEnd, so that all Inputs are inside that new div and can be easily targeted using CSS or JS, or....)
Complete example:
library(shiny)
ModularizedUI <- function(id) {
ns <- NS(id)
fluidPage(
# Input: Action button to add text field
actionButton(inputId = ns("add_text"),
label = "Add text field"),
div(id = "add")
)
}
ModularizedServer <- function(id) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
# Track the number of input boxes to render for test training
counter_text <- reactiveVal(0)
# Input/Output fields for start dates test training
observeEvent(input$add_text, {
counter_text(counter_text() + 1)
# Add UI if this button is clicked
insertUI(
selector = "#add",
where = "beforeEnd",
ui = textInput(inputId = ns(paste0("textfield_", counter_text())),
label = "New text")
)
})
}
)
}
ui <- navbarPage("Dashboard",
tabPanel("Text fields",
ModularizedUI(id = "Text_Fields")))
server <- function(input, output, session) {
ModularizedServer("Text_Fields")
}
shinyApp(ui = ui, server = server)
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 currently rewriting a big shinyapp and I try to shift as much as possible into modules.
At some point, the user can choose weather to use stuff that is inside box a) or inside box b).
I know how to toggle or remove / restore a box in shiny, but I ran across a problem when using shinymodules: Inside the ui-function, I have a radiobutton, and the server-function should just observe its's value and hide or show a box according to the input. Well, the actual box to hide or show ist not inside the module because it is filled with another module.
Please see the code below for an example, you'll see that the box won't be removed or restored or whatever.
Maybe someone has an idea how to fix this or sees where I make a mistake?
Thank you!
# ui ----
testUI <- function(id){
tagList(
radioGroupButtons(NS(id, "switch"), label = NULL, individual = T,
choices = c("show", "dont show"), selected = "dont show"),
)
}
# server ----
testServer <- function(id, boxid){
moduleServer(id, function(input, output, session){
observeEvent(input$switch, {
if(input$switch == "show"){
updateBox(id = boxid, action = "restore", session = session)
} else {
updateBox(id = boxid, action = "remove", session = session)
}
})
})
}
# testing ----
testApp <- function(){
# create ui
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
testUI("zg"),
box(id = "mybox", title = "I am a box",
strong("some content")
) # end box
) # end dashboardBody
) # end dahsboardPage
# create server
server <- function(input, output, session){
testServer("zg", boxid = "mybox")
}
# start server
shinyApp(ui, server)
}
# start small app for testing (comment if not in use)
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyWidgets)
testApp()
Normally, in order to use a updateXXX function in a module, the widget to be updated must be in the UI part of the module.
But that does not work with updateBox, I don't know why (I think the package author should add something in the implementation). See the example below. The updateRadioButtons works, but not the updateBox.
testUI <- function(id){
ns <- NS(id)
tagList(
radioButtons( # this widget will be updated
ns("switch"), label = NULL,
choices = c("show", "dont show"), selected = "show"
),
box( # this widget will *not* be updated
id = ns("mybox"), title = "I am a box", strong("some content")
)
)
}
# server ----
testServer <- function(id, boxid){
moduleServer(id, function(input, output, session){
observeEvent(input$switch, {
if(input$switch == "show"){
updateBox(id = boxid, action = "restore", session = session)
updateRadioButtons(session, "switch", label = "HELLO")
} else {
updateBox(id = boxid, action = "remove", session = session)
updateRadioButtons(session, "switch", label = "GOODBYE")
}
})
})
}
Background
I am using {brochure} and {golem} to build a shiny app. I have one outer module grid that consists of inner modules subGrid2 which displays the same module UI on two tabs.
GOAL
have a module subGrid2 that can be used for repeating graph
visualizations on multiple tabs.
in the REPREX --> fake graph generated from {shinipsum} to
be displayed on the "Home" tab + "Portfolio" tab
use observeEvent to look at the slected tab and generate server response respectivley
Problem
The observeEvent reactive expr. fails to recognize when the corresponding tab is selected to generate the correct server response.
-using the reprex below replicates my issue-
TL/DR
Why wont the observeEvent reactive generate the correct server response per the selected tab?
REPREX
uncomment observeEvent to see error
#22.2.22
library(brochure)
library(shiny)
library(shinipsum)
library(shinydashboard)
library(shinydashboardPlus)
mod_subGrid2_ui <- function(id) {
ns <- NS(id)
tagList(
plotOutput(ns("plot"))
)
}
mod_subGrid2_server <- function(id) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
output$plot <- renderPlot({
shinipsum::random_ggplot()
})
})
}
#Setup dashboard
mod_Grid_ui <- function(id) {
ns <- NS(id)
shinydashboardPlus::dashboardPage(
skin = "midnight",
header = dashboardHeader(title = "test"),
sidebar = dashboardSidebar(
shinydashboard::sidebarMenu(
# Setting id makes input$tabs give the tabName of currently-selected tab
id = "tabs",
menuItem("Home", tabName = "home", icon = icon("tachometer-alt")),
menuItem("Portfolio", tabName = "portfolio", icon = icon("chart-line"), badgeLabel = "new",
badgeColor = "green")
)
),
body = shinydashboard::dashboardBody(
# Enable shinyjs
shinyjs::useShinyjs(),
shinydashboard::tabItems(
shinydashboard::tabItem("home",
shiny::tagList(
div(p("Content for 1st tab goes here -- GRID MODULE")),
mod_subGrid2_ui(ns("subGrid2_ui_1"))
)
),
shinydashboard::tabItem("portfolio",
shiny::tagList(
div(p("Content for 2nd goes here -- GRID MODULE (2x)")),
titlePanel(title = "The same module UI goes here"),
mod_subGrid2_ui(ns("subGrid2_ui_2"))
)
)
)
)
)
}
mod_Grid_server <- function(id) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
mod_subGrid2_server("subGrid2_ui_1")
mod_subGrid2_server("subGrid2_ui_2")
## uncomment to try
# observeEvent(input$tabs,{
# if(input$tabs == "home"){
# # <subGrid> server fragment
# mod_subGrid2_server("subGrid2_ui_1")
# } else if(input$tabs == "portfolio"){
# mod_subGrid2_server("subGrid2_ui_1")
# }
# }, ignoreNULL = TRUE, ignoreInit = TRUE)
})
}
brochureApp(
page(
href = "/",
ui = tagList(
mod_Grid_ui("grid_1")
),
server = function(input, output, session) {
mod_Grid_server("grid_1")
}
),
wrapped = shiny::tagList
)
When using a module nested inside another module, you need to ns() the id of the nested UI function.
So here, mod_subGrid2_ui(ns("subGrid2_ui_1")).
Here is a minimal reprex:
mod_subGrid2_ui <- function(id) {
ns <- NS(id)
tagList(
plotOutput(ns("plot"))
)
}
mod_subGrid2_server <- function(id) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
output$plot <- renderPlot({
shinipsum::random_ggplot()
})
})
}
mod_Grid_ui <- function(id) {
ns <- NS(id)
tagList(
mod_subGrid2_ui(ns("subGrid2_ui_1"))
)
}
mod_Grid_server <- function(id) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
mod_subGrid2_server("subGrid2_ui_1")
})
}
brochureApp(
page(
href = "/",
ui = tagList(
mod_Grid_ui("grid_1")
),
server = function(input, output, session) {
mod_Grid_server("grid_1")
}
)
)
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)
I would like to set up my Shiny app to dynamically enable/disable UI modules based on user input. I am accustomed to using ShinyJS to do this in a non-modular app by passing the ID of the UI element into the enable() or disable() functions. However, with the UI now being generated inside of a module, I no longer have access to the same ID.
Here is an example app which increments by 1 each time the "counter1" button is clicked. The "counterButton" function is contained in an external module called "counterModule.R", and I would like the "toggleButton" to toggle the state of the "counterButton" between enabled and disabled. The call to toggleState() currently does nothing I assume because the "counter1" ID is not found. What would be the best way of going about this?
app.R
library(shiny)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
mainPanel(actionButton(inputId = "toggleButton", label = "Toggle counter button"),
sidebarPanel(counterButton("counter1", "+1")))
)
server <- function(input, output, session) {
observeEvent(input$toggleButton, {
print("clicked toggle button")
shinyjs::toggleState("counter1")
})
counterServer("counter1")
}
shinyApp(ui, server)
R/counterModule.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
}
)
}
You have 2 possibilities. The namespacing of the shiny modules works in the following format: namespace-elementid. This means that your button in the module has the id counter1-button which is globally unique (and within in the module, you can just use button).
Therefore, you can use the namespaced id in your main server function:
observeEvent(input$toggleButton, {
print("clicked toggle button")
shinyjs::toggleState("counter1-button")
})
However, this somehow breaks the separation of ui/logic defined in the module and in the main server function. Therefore, the second option is to define the toggle button in the main app, but have the toggle logic in the module:
library(shiny)
library(shinyjs)
##########################
# code of the module
counterButton <- function(id, label = "Counter") {
ns <- NS(id)
tagList(
actionButton(ns("button"), label = label),
verbatimTextOutput(ns("out"))
)
}
counterServer <- function(id, toggle_action) {
moduleServer(
id,
function(input, output, session) {
count <- reactiveVal(0)
observeEvent(input$button, {
count(count() + 1)
})
output$out <- renderText({
count()
})
observeEvent(toggle_action(), {
print("clicked toggle button")
shinyjs::toggleState("button")
})
count
}
)
}
##########################
# code of the main app
ui <- fluidPage(
shinyjs::useShinyjs(),
mainPanel(actionButton(inputId = "toggleButton", label = "Toggle counter button"),
sidebarPanel(counterButton("counter1", "+1")))
)
server <- function(input, output, session) {
counterServer("counter1", toggle_action = reactive({input$toggleButton}))
}
shinyApp(ui, server)