Reputation: 93
I'm trying to allow the user to add successive UI output seen in server_module2 from another module (ui_module1). So, when they hit the button, they will see 3 UI objects: textOutput, sliderInput, textInput. My code below stops at outputting the UI object. If I don't wrap it in the 1st module, it works fine.
Thank you.
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyWidgets)
library(dplyr)
ui_module2 = function(id) {
ns = NS(id)
uiOutput(ns("finalOut"))
}
server_module2 = function(id) {
moduleServer(id,
function(input, output, session) {
output$finalOut = renderUI({
ns = session$ns
textOutput(p(style = "color: red", paste0("Package Num:", id, sep = "-")))
sliderInput("n", "N", 1, 1000, 500)
textInput("label", "Label")
})
})
}
ui_module1 = function(id) {
ns = NS(id)
actionBttn(ns("actionbutton1"), "Press Button For New UI")
}
server_module1 = function(id) {
moduleServer(id,
function(input, output, session) {
observeEvent(input$actionbutton1, {
i = sprintf('%04d', input$actionbutton1)
id = sprintf('static%s', i)
print(id)
insertUI(selector = "#actionbutton1",
where = "afterEnd",
ui = ui_module2(id))
server_module2(id)
})
})
}
ui = fluidPage(ui_module1("opt)"))
server = function(input, output, session) {
server_module1("opt)")
}
shinyApp(ui, server)
Upvotes: 3
Views: 800
Reputation: 7340
Here is the working code. A lot you need to notice. Here are some key points.
ns
for all modules.renderUI
only returns one object, if you want to return more than one components, use tagList
or div
.insertUI
selector inside module also needs to add ns
.Check the code yourself for minor bugs.
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyWidgets)
library(dplyr)
ui_module2 = function(id) {
ns = NS(id)
uiOutput(ns("finalOut"))
}
server_module2 = function(id) {
moduleServer(id,
function(input, output, session) {
output$finalOut = renderUI({
ns = session$ns
div(
# not sure what you want with next line, commented, fix your own
# textOutput(p(style = "color: red", paste0("Package Num:", id, sep = "-"))),
sliderInput(ns("n"), "N", 1, 1000, 500),
textInput(ns("label"), "Label")
)
})
})
}
ui_module1 = function(id) {
ns = NS(id)
actionBttn(ns("actionbutton1"), "Press Button For New UI")
}
server_module1 = function(id) {
moduleServer(id,
function(input, output, session) {
ns <- session$ns
observeEvent(input$actionbutton1, {
i = sprintf('%04d', input$actionbutton1)
id = sprintf('static%s', i)
print(id)
insertUI(selector = paste0("#", ns("actionbutton1")),
where = "afterEnd",
immediate = TRUE,
ui = ui_module2(ns(id)))
server_module2(id)
})
})
}
ui = fluidPage(ui_module1("opt"))
server = function(input, output, session) {
server_module1("opt")
}
shinyApp(ui, server)
This case is an example of nested modules. To understand this, you need to know how ns
namespace works in Shiny.
As Shiny's official document, they tell you that you need to add ns(id)
on the module UI and you don't need to do so for the module server, but they didn't tell you why.
When you call the moduleServer(id, function(input, output, session) {...})
, the session here is different than your top-level session. If you check the session here, it's a session_proxy
class object, no longer the ShinySession
class object. This is how shiny internally knows how expressions under moduleServer
are inside the module. When you call input
, output
, renderXX
, updateXX
methods under the module scope, it will first query the session
object, and if it is session_proxy
, call the session$ns
to attach namespace first. This is why you don't need to add ns()
on the server, shiny did it for you.
Ok, let's go back to the nested module problem. Let's see what's namespace on both UI and server with this simple example. All it does is print out the namespace on UI and server.
library(shiny)
uiMod2 <- function(id) {
ns <- NS(id)
div(id = ns(""))
}
serverMod2 <- function(id) {
moduleServer(
id,
function(input, output, session) {
ns <- session$ns
print(ns(""))
})
}
uiMod1 <- function(id) {
ns <- NS(id)
tagList(
div(id = ns("")),
uiMod2(ns("level2"))
)
}
serverMod1 <- function(id) {
moduleServer(
id,
function(input, output, session) {
ns <- session$ns
print(ns(""))
serverMod2("level2")
})
}
ui <- fluidPage(uiMod1("level1"))
server <- function(input, output, session) {serverMod1("level1")}
shinyApp(ui, server)
Results UI
Server
[1] "level1-"
[1] "level1-level2-"
See the difference? I didn't call ns
for second-level server, only serverMod2("level2")
, but the namespace is automatically attached for me. However, shiny doesn't know to apply this to the UI, so I have to use uiMod2(ns("level2"))
.
The reason why shiny doesn't know is all about session
. When you create a module, you need to pass function(input, output, session)
, and when you create nested modules, this session
for the second level module is not the ShinySession
, but a session_proxy
, so parent namespace(s) will be appended automatically.
Things are different with UI. Normally When you create module UI like uiMod2 <- function(id) ...
, you don't pass the session object, so it doesn't know what namespace to inherit.
Upvotes: 6