将 UI 插入 R shiny moduleServer

Inserting UI into R shiny moduleServer

我正在尝试允许用户从另一个模块 (ui_module1) 添加在 server_module2 中看到的连续 UI 输出。因此,当他们点击按钮时,他们将看到 3 UI 个对象:textOutput、sliderInput、textInput。我下面的代码在输出 UI 对象时停止。如果我不将它包装在第一个模块中,它就可以正常工作。

谢谢。

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)

这是工作代码。很多你需要注意的。以下是一些要点。

  1. 记得为所有模块添加ns
  2. renderUI只有return一个对象,如果你想return多个组件,使用tagListdiv
  3. insertUI 模块内部的选择器也需要添加 ns.

自己检查代码是否存在小错误。

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)

了解它是如何工作的

这个案例是嵌套模块的一个例子。要理解这一点,您需要了解 ns 命名空间在 Shiny 中是如何工作的。 作为Shiny的官方文档,他们告诉你你需要在模块UI上添加ns(id)而你不需要为模块服务器添加,但他们没有告诉你为什么。

当您调用 moduleServer(id, function(input, output, session) {...}) 时,此处的会话与顶级会话不同。如果您在此处检查会话,它是一个 session_proxy class 对象,不再是 ShinySession class 对象。这就是闪亮内部如何知道 moduleServer 下的表达式在模块内部的方式。在模块作用域下调用inputoutputrenderXXupdateXX方法时,会先查询session对象,如果是session_proxy,先调用session$ns附加命名空间。这就是为什么你不需要在服务器上添加 ns() 的原因,shiny 为你做了。

好了,我们回到嵌套模块问题。让我们通过这个简单的例子看看 UI 和服务器上的命名空间是什么。它所做的只是在 UI 和服务器上打印出命名空间。

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)

结果UI

服务器

[1] "level1-"
[1] "level1-level2-"

看出区别了吗?我没有为二级服务器调用 ns,只调用 serverMod2("level2"),但命名空间已自动附加给我。但是,shiny 不知道将其应用于 UI,所以我必须使用 uiMod2(ns("level2")).

shiny之所以不知道,全在于session。创建模块时需要传入function(input, output, session),而创建嵌套模块时,二级模块的这个session不是ShinySession,而是一个session_proxy , 因此将自动附加父命名空间。

UI 情况不同。通常当你像uiMod2 <- function(id) ...一样创建模块UI时,你没有传递会话对象,所以它不知道要继承什么命名空间。