ObserveEvent not triggered in nested module inserted with insertUI - r

I can create a module that inserts a button and triggers a browser() call inside an observeEvent() when the button is clicked:
library(shiny)
mod_ui <- function(id){
ns <- NS(id)
actionButton(ns("test"), "Test")
}
mod_server <- function(id) {
moduleServer(id, function(input, output, session){
ns <- session$ns
observeEvent(input$test, {
browser()
})
})
}
ui <- fluidPage(
mod_ui("mod_top")
)
server <- function(input, output, session){
mod_server("mod_top")
}
shinyApp(ui = ui, server = server)
But if I insert this module inside another module using insertUI, the browser call is no longer triggered:
library(shiny)
mod_ui <- function(id) {
ns <- NS(id)
div(
id = ns("place_here"),
actionButton(ns("add"), "Add")
)
}
mod_server <- function(id) {
moduleServer(id, function(input, output, session){
ns <- session$ns
observeEvent(input$add, {
insertUI(
immediate = TRUE,
selector = paste0("#", ns("place_here")),
where = "beforeEnd",
ui = mod_ui2(ns("mod_inner"))
)
mod_server2(ns("mod_inner"))
})
})
}
mod_ui2 <- function(id){
ns <- NS(id)
actionButton(ns("test"), "Test")
}
mod_server2 <- function(id) {
moduleServer(id, function(input, output, session){
ns <- session$ns
observeEvent(input$test, {
browser()
})
})
}
ui <- fluidPage(
mod_ui("mod_top")
)
server <- function(input, output, session){
mod_server("mod_top")
}
shinyApp(ui = ui, server = server)
How do I trigger the browser call?

Just needed to remove the namespacing of the nested server module:
mod_server2("mod_inner")
Here is the full working app:
library(shiny)
mod_ui <- function(id) {
ns <- NS(id)
div(
id = ns("place_here"),
actionButton(ns("add"), "Add")
)
}
mod_server <- function(id) {
moduleServer(id, function(input, output, session){
ns <- session$ns
observeEvent(input$add, {
insertUI(
immediate = TRUE,
selector = paste0("#", ns("place_here")),
where = "beforeEnd",
ui = mod_ui2(ns("mod_inner"))
)
mod_server2("mod_inner")
})
})
}
mod_ui2 <- function(id){
ns <- NS(id)
actionButton(ns("test"), "Test")
}
mod_server2 <- function(id) {
moduleServer(id, function(input, output, session){
ns <- session$ns
observeEvent(input$test, {
browser()
})
})
}
ui <- fluidPage(
mod_ui("mod_top")
)
server <- function(input, output, session){
mod_server("mod_top")
}
shinyApp(ui = ui, server = server)

Now I understand what you were trying to do. Needed to remove the ns() from mod_server2. Also, as pointed out in previous response, needed to correct for input$test.
library(shiny)
mod_ui <- function(id) {
ns <- NS(id)
div(
id = ns("place_here"),
actionButton(ns("add"), "Add")
)
}
mod_server <- function(id) {
moduleServer(id, function(input, output, session){
observeEvent(input$add, {
ns <- session$ns
insertUI(
immediate = TRUE,
selector = paste0("#", ns("place_here")),
where = "beforeEnd",
ui = mod_ui2(ns("mod_inner"))
)
mod_server2("mod_inner")
})
})
}
mod_ui2 <- function(id){
ns <- NS(id)
actionButton(ns("test"), "Test")
}
mod_server2 <- function(id) {
moduleServer(id, function(input, output, session){
observeEvent(input$test, {
browser()
})
})
}
ui <- fluidPage(
mod_ui("mod_top")
)
server <- function(input, output, session){
mod_server("mod_top")
}
shinyApp(ui = ui, server = server)

Related

Separate the `renderUI` in a module server

In some complex and large shiny apps, the UI parts are often rendered with renderUI and uiOutput. Consider this small app for illustration:
library(shiny)
modUI <- function(id) {
ns <- NS(id)
uiOutput(ns("theUI"))
}
modServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
output[["plot"]] <- renderPlot({
n <- input[["numb"]]
plot(rnorm(n), rnorm(n), pch = 19L)
})
ns <- session$ns
output[["theUI"]] <- renderUI({
tagList(
sliderInput(ns("numb"), "N", 10, 100, 50),
plotOutput(ns("plot"))
)
})
}
)
}
ui <- basicPage(
br(),
modUI("myapp")
)
server <- function(input, output, session) {
modServer("myapp")
}
shinyApp(ui, server)
Here modUI is ridiculously small. But in modServer, the renderUI could be large and there could be many other output components. Therefore it is desirable to split modServer:
renderPlotServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
output[["plot"]] <- renderPlot({
n <- input[["numb"]]
plot(rnorm(n), rnorm(n), pch = 19L)
})
}
)
}
renderUIServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
ns <- session$ns
output[["theUI"]] <- renderUI({
tagList(
sliderInput(ns("numb"), "N", 10, 100, 50),
plotOutput(ns("plot"))
)
})
}
)
}
and to put them together one can use the same id:
modServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
renderPlotServer("mysubapp")
renderUIServer("mysubapp")
}
)
}
but then we need a nested namespace in the UI part of the module:
modUI <- function(id) {
ns <- NS(NS(id)("mysubapp"))
uiOutput(ns("theUI"))
}
This is not convenient.
A more convenient solution consists in using a NULL namespace, to avoid the nested namespace in the UI part:
modServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
renderPlotServer(NULL)
renderUIServer(NULL)
}
)
}
But this solution has a problem: it prevent to use the module multiple times.
So, what would be a convenient solution avoiding this problem?
I think I have one: one can nest the "main" id with itself:
modServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
renderPlotServer(id)
renderUIServer(id)
}
)
}
modUI <- function(id) {
ns <- NS(NS(id)(id))
uiOutput(ns("theUI"))
}
Do you have another nice solution?
So maybe I am overlooking some subtleties of your approach, but why would you need to use moduleServer in render(Plot|UI)Server in the first place? Rather you can go for a plain function and all the namespacing seems to work without any further ado?
library(shiny)
modUI <- function(id) {
ns <- NS(id)
uiOutput(ns("theUI"))
}
renderPlotServer <- function(input, output, session) {
output[["plot"]] <- renderPlot({
n <- input[["numb"]]
plot(rnorm(n), rnorm(n), pch = 19L)
})
}
renderUIServer <- function(input, output, session) {
ns <- session$ns
output[["theUI"]] <- renderUI({
tagList(
sliderInput(ns("numb"), "N", 10, 100, 50),
plotOutput(ns("plot"))
)
})
}
modServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
renderPlotServer(input, output, session)
renderUIServer(input, output, session)
}
)
}
ui <- basicPage(
br(),
modUI("myapp"),
br(),
modUI("myapp2") ## a second module works like a charm
)
server <- function(input, output, session) {
modServer("myapp")
modServer("myapp2")
}
shinyApp(ui, server)

How to update input in shiny modlue from another shiny module?

I've got two shiny modules, with updateTextInput() in the first one. I want to update textInput() in the second module, when button from the first is clicked. I know it's because those modules are in different namespaces but I can't figure out how to communicate modules.
Reprex below :)
library(shiny)
firstUI <- function(id) {
ns <- NS(id)
tagList(
actionButton(ns("update"), "Update 1st and 2nd module"),
textInput(ns("first"), "Update me pls1", value = "Clear me!")
)
}
firstServer <- function(id) {
moduleServer(id, function(input, output, session) {
observeEvent(input$update, {
updateTextInput(session = session, "first", value = "")
updateTextInput(session = session,"second", value = "")
})
})
}
secondUI <- function(id) {
ns <- NS(id)
tagList(
textInput(ns("second"), "Update me pls", value = "Clear me!")
)
}
secondServer <- function(id) {
moduleServer(id, function(input, output, session) {
observeEvent(input$update, {
updateTextInput(session = session, "first", value = "")
updateTextInput(session = session,"second", value = "")
})
})
}
ui <- fluidPage(
firstUI("module_one"),
secondUI("module_two")
)
server <- function(input, output, session) {
firstServer("module_one")
secondServer("module_two")
}
shinyApp(ui, server)
You can do it by making the first input$update reactive, then returning that value and making it reactive to the second server module. This way the second server module is "listening" to the change in the first one.
library(shiny)
firstUI <- function(id) {
ns <- NS(id)
tagList(
actionButton(ns("update"), "Update 1st and 2nd module"),
textInput(ns("first"), "Update me pls1", value = "Clear me!")
)
}
firstServer <- function(id) {
moduleServer(id, function(input, output, session) {
observeEvent(input$update, {
updateTextInput(session = session, "first", value = "")
updateTextInput(session = session,"second", value = "")
})
reactive(input$update)
})
}
secondUI <- function(id) {
ns <- NS(id)
tagList(
textInput(ns("second"), "Update me pls", value = "Clear me!")
)
}
secondServer <- function(id, clear) {
moduleServer(id, function(input, output, session) {
observeEvent(clear(), {
updateTextInput(session = session, "first", value = "")
updateTextInput(session = session,"second", value = "")
})
})
}
ui <- fluidPage(
firstUI("module_one"),
secondUI("module_two")
)
server <- function(input, output, session) {
clear <- reactive(firstServer("module_one"))
secondServer("module_two", clear())
}
shinyApp(ui, server)
A roundabout way would be to use shinyjs to trigger the updating manually.
library(shiny)
library(shinyjs)
firstUI <- function(id) {
ns <- NS(id)
tagList(
actionButton(ns("update"), "Update 1st and 2nd module"),
textInput(ns("first"), "Update me pls1", value = "Clear me!")
)
}
firstServer <- function(id) {
moduleServer(id, function(input, output, session) {
observeEvent(input$update, {
updateTextInput(session = session, "first", value = "")
runjs('document.getElementById("module_two-second").value = ""')
})
})
}
secondUI <- function(id) {
ns <- NS(id)
tagList(
textInput(ns("second"), "Update me pls", value = "Clear me!")
)
}
secondServer <- function(id) {
moduleServer(id, function(input, output, session) {
# Code not needed in here for now
})
}
ui <- fluidPage(
useShinyjs(),
firstUI("module_one"),
secondUI("module_two")
)
server <- function(input, output, session) {
firstServer("module_one")
secondServer("module_two")
}
shinyApp(ui, server)
Shiny modules work by giving each element a unique id by pasting [module_name]-[element_id] together in the html frontend, so each module server can correctly identify which it should be talking to. The first server can find and talk to module_two-second when passed that id directly. Ideally there might be a way of doing this within the Shiny code itself though.
Edit: fix within Shiny by passing parent_session (without shinyjs)
The updateTextInput call can indeed find module_two-second itself if it can look outside of its own session environment. To achieve this, you can pass the parent_session as the argument to updateTextInput (defined in firstServer function definition and passed as parent_session = session in the server body):
library(shiny)
firstUI <- function(id) {
ns <- NS(id)
tagList(
actionButton(ns("update"), "Update 1st and 2nd module"),
textInput(ns("first"), "Update me pls1", value = "Clear me!")
)
}
firstServer <- function(id, parent_session) {
moduleServer(id, function(input, output, session) {
observeEvent(input$update, {
updateTextInput(session = session, "first", value = "")
updateTextInput(session = parent_session, "module_two-second", value = "")
})
})
}
secondUI <- function(id) {
ns <- NS(id)
tagList(
textInput(ns("second"), "Update me pls", value = "Clear me!")
)
}
secondServer <- function(id) {
moduleServer(id, function(input, output, session) {
# Code not needed in here for now
})
}
ui <- fluidPage(
firstUI("module_one"),
secondUI("module_two")
)
server <- function(input, output, session) {
firstServer("module_one", parent_session = session)
secondServer("module_two")
}
shinyApp(ui, server)

Shiny server function finds module when using fixed IDs but not when using ns()

I have a shiny app, each module is it's own file. Each module get's an ns <- NS(id). When I adress an Element, say a button from one of those modules with observeEvent it works if I just hardcode an ID in the module, but not if I use ns(). What am I doing wrong?
Module:
mod_add_element_ui <- function(id){
ns <- NS(id)
tagList(
shiny::actionButton(ns("add_element"), "add new element", icon = icon("plus-square"))
)
}
mod_add_element_server <- function(id){
moduleServer( id, function(input, output, session){
ns <- session$ns
})
}
app_ui:
app_ui <- function(request) {
tagList(
fluidPage(
mod_add_element_ui("add_element_ui_1"),
div(id="add_here")
)
)
}
app_server:
app_server <- function( input, output, session ) {
mod_add_element_server("add_element_ui_1")
observeEvent(input$add_element,
{
mod_add_element_server(id="mod")
insertUI(selector = "#add_here", ui = mod_add_element_ui("mod"))
}
)
}
Try this
mod_add_element_ui <- function(id){
ns <- NS(id)
tagList(
shiny::actionButton(ns("add_element"), "add new element", icon = icon("plus-square"))
)
}
mod_add_element_server <- function(id){
moduleServer( id, function(input, output, session){
ns <- session$ns
return(reactive(input$add_element))
})
}
app_ui <- function(request) {
tagList(
fluidPage(
mod_add_element_ui("add_element_ui_1"),
div(id="add_here")
)
)
}
app_server <- function( input, output, session ) {
added_element <- mod_add_element_server("add_element_ui_1")
observeEvent(added_element(),
{
mod_add_element_server(id="mod")
insertUI(selector = "#add_here", ui = mod_add_element_ui("mod"))
}
)
}
shinyApp(app_ui, app_server)

eventReactive in shiny module

I would like to use an eventReactive-function in a shiny module. However that does not work as expected. What is wrong with my code or what do I have to add?
I have already tried observers but I want to use eventReactive because I need the return-value.
mod_test_UI <- function(id) {
ns <- NS(id)
actionButton(ns("test"), "Test")
}
mod_test <- function(input, output, session) {
ns <- session$ns
observe({
print(input$test)
})
result<- eventReactive(input$test, {
print("ABC")
})
}
ui <- tagList(
mod_test_UI("test-mod")
)
server <- function(input, output, session) {
callModule(mod_test, "test-mod")
}
# app
shinyApp(ui = ui, server = server)
You need to return a value within eventReactive as below:
mod_test_UI <- function(id) {
ns <- NS(id)
actionButton(ns("test"), "Test")
}
mod_test <- function(input, output, session) {
ns <- session$ns
observe({
print(input$test)
})
result<- eventReactive(input$test, {
return("ABC")
})
observe({
print(result())
})
}
ui <- tagList(
mod_test_UI("test-mod")
)
server <- function(input, output, session) {
callModule(mod_test, "test-mod")
}
# app
shinyApp(ui = ui, server = server)
The second observe just prints the value now contained in result() to the screen to prove that it works.
The return() in this case is not necessary and it could just be "ABC" as below:
result<- eventReactive(input$test, {
"ABC"
})

Using shiny's renderUI in Module

this is my first question at stackoverflow. I have a problem with modules and renderUI in shiny (1.0.5).
When I use renderUI in
#### Main Part
ui <- bootstrapPage(
uiOutput("DynamicContent")
)
server <- function(input, output,session) {
S_A <- selectInput("S_A_Input" ,"Change Me for print message",choices=1:3 )
output$DynamicContent <- renderUI({
tagList(S_A)
})
observe({
print(input$S_A_Input)
})
}
shinyApp(ui = ui, server = server)
then changing the selectInput will cause changing input$S_A_Input, so the print will occur. That's fine.
On the other hand, input$S_A_Input seems not to work, if I work with modules:
### Module Part
Module_YYY_Server <- function(input, output, session){
S_A <- selectInput("S_A_Input" ,"Change Me for print message",choices=1:3 )
output$DynamicContent <- renderUI({
tagList(S_A)
})
observe({
print(input$S_A_Input)
})
}
Module_YYY_Ui <- function(id){
ns <- NS(id) # Creates Namespace
tagList(
uiOutput("DynamicContent" %>% ns)
)
}
And then calling the module.
#### Main Part
ui <- bootstrapPage(
Module_YYY_Ui("YYY")
)
server <- function(input, output,session) {
callModule(Module_YYY_Server,"YYY")
}
shinyApp(ui = ui, server = server)
I haven't found a solution to this behaviour.
Late to the party but this is another option to solve your problem using
session$ns("id")
Here how it looks like:
Module_YYY_Server <- function(input, output, session){
output$DynamicContent <- renderUI({
selectInput(session$ns("S_A_Input"), "Change Me for print message", choices = 1:3)
})
output$text <- renderText({
req(input$S_A_Input)
input$S_A_Input})
}
Module_YYY_Ui <- function(id){
ns <- NS(id) # Creates Namespace
tagList(
uiOutput(ns("DynamicContent")),
textOutput(ns("text"))
)
}
ui <- bootstrapPage(
Module_YYY_Ui("YYY")
)
server <- function(input, output,session) {
callModule(Module_YYY_Server,"YYY")
}
shinyApp(ui = ui, server = server)
This is based on the example here
While I am sure your example is simplified, If your input$S_A_Input is not changing based on what you are doing, as is the case in your example, I would not recommend using renderUI. Regardless of that, the reason that your input is not printing is because you are not generating it as an output.
Module_YYY_Server <- function(input, output, session){
output$DynamicContent <- renderUI({
ns <- session$ns
tagList(
selectInput("S_A_Input" %>% ns, "Change Me for print message",choices=1:3 )
)
})
output$text <- renderText({input$S_A_Input})
}
Module_YYY_Ui <- function(id){
ns <- NS(id) # Creates Namespace
tagList(
uiOutput("DynamicContent" %>% ns),
textOutput("text" %>% ns)
)
}

Resources