NellieG
NellieG

Reputation: 93

Inserting UI into R shiny moduleServer

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

Answers (1)

lz100
lz100

Reputation: 7340

Here is the working code. A lot you need to notice. Here are some key points.

  1. Remember to add ns for all modules.
  2. renderUI only returns one object, if you want to return more than one components, use tagList or div.
  3. 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)

understand how it works

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

enter image description here

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

Related Questions