The app below contains a selectInput
(input$month
) and a button, Show Modal
, that launches a modal window when clicked. The modal contains a button, Add UI
, that inserts some text, Element x
, where x
is the value of a counter that increases by 1 each time Add UI
is clicked.
Screenshot of app on startup with the modal window opened:
The app is modularised so that UI associated with the modal is rendered by function modUI
and the corresponding server logic is defined in function modServer
. modUI
is rerendered each time input$month
changes to overwrite previously inserted text elements.
I require the first block of text to be inserted programmatically, so that when the app loads OR the user changes input$month
, Element 1
is already rendered in the modal. To do this, I tried to get the insertUI observer to fire if input$add_ui
greater than or equal to 0 - i.e. observeEvent(if(req(input$add_ui) >= 0) TRUE else return(), { #insertUI expr })
. However, this doesn't work and I don't understand why. Since observers are eagerly evaluated, shouldn't this observer fire after input$add_ui
has finished initialising?
The counter must also be reset to 0 each time input$month
changes so that inserted text elements start at Element 1
. To do this, I included the following observer in modServer
:
observe({
req(input$add_ui == 0) #checking that modUI has finished rerendering
print(paste('month changed to:', month, 'resetting counter'));
counter(0)
})
Lastly, I am relatively new to modules and was wondering if someone could explain why counter()
is not reset automatically whenever modServer
is called via observe(callModule(modServer, 'hi', month = input$month))
? Why does its value persist if the module has its own environment?
I would be grateful for any help as I have been stuck on this for a while.
Code to reproduce the above:
library(shiny)
library(shinyBS)
#MODULE UI ----
modUI <- function(id) {
ns <- NS(id)
tagList(
actionButton(ns("show_modal"), "Show modal"),
bsModal(
id = ns('modal'),
trigger = ns('show_modal'),
actionButton(ns("add_ui"), "Add UI"),
tags$div(id = ns("placeholder1"))
)
)
}
#MODULE SERVER ----
modServer <- function(input, output, session, month) {
ns <- session$ns
counter <- reactiveVal(0)
# Observer to insert UI element
observeEvent(if(req(input$add_ui) >= 0) TRUE else return(), {
counter(counter() + 1)
insertUI(
selector = paste0("#", ns("placeholder1")),
ui = tags$div(paste('Element', counter()))
)
})
# Reset counter() if month is changed
observe({
print(paste('month changed to:', month(), 'resetting counter'));
counter(0)
}, ignoreInit = T)
# Print
observe({ print(paste('input$add_ui:', input$add_ui, 'counter:', counter())) })
}
#MAIN UI ----
ui <- fluidPage(
tagList(
selectInput('month', 'Month', month.abb),
uiOutput('modal_ui')
)
)
#MAIN SERVER ----
server <- function(input, output, session) {
#Call modUI if input$month is changed
callModule(modServer, 'hi', month = reactive(input$month)) #previously observe(callModule(modServer, 'hi', month = input$month)), why isn't this allowed?
#Rerender modUI if input$month is changed
output$modal_ui <- renderUI({
input$month
modUI('hi')
})
observe(print(input$month))
}
shinyApp(ui = ui, server = server)