简体   繁体   中英

R shinyapp: renderUI does not work with nested modules R6 classes

I have a big shiny app with modules and I am converting some of these in R6 classes. All works well except for nested modules: I am facing an issue with namespaces and the uiOutput does not work.

Here is a reproductible example. The class ClassTest is the last class of the hierarchy of classes. It is called either directly by the App or via the Temp class. In the latter case (nested class), the elements contained in the uiOutput does not work.

library(shiny); library(R6)

ClassTest = R6Class(
  "ClassTest",
  public = list(
    # attributes
    id = NULL,

# initialize
initialize = function(id){
  self$id = id
},
# UI
ui = function(){
  ns = NS(self$id)
  tagList(
    h4('Test class'),
    uiOutput(ns('showText'))
  )
},

# server
server = function(id){
  moduleServer(id,
               function(input, output, session){
  ns = session$ns

  output$showText <- renderUI({ 
    print('In showText <- renderUI')
    tagList(
      p('I am the showText renderUI of the Test class')
      )
    })
}
)
  },

call = function(input, ouput, session){
  self$server(self$id)
}
     )
    )

#----------------------------------------------------------


Temp = R6Class(
  "Temp",
  public = list(
    # attributes
    temp = NULL,
    id = NULL,

# initialize
initialize = function(id){
  self$id = id
  self$temp <- ClassTest$new('testTiers')
},
# UI
ui = function(){
  ns = NS(self$id)
  tagList(
    uiOutput(ns('showTestClass'))
  )
},

# server
server = function(id){
  moduleServer(id,
               function(input, output, session){
                 ns = session$ns 
   output$showTestClass <- renderUI({ 
    self$temp$ui()
    })
})
  },

call = function(input, ouput, session){
  self$server(self$id)
}
  )
)

#----------------------------------------------------------


App = R6Class(
  "App",
  public = list(
    # attributes
    temp = NULL,
    classT = NULL,

# initialize
initialize = function(){
  self$temp = Temp$new('temp')
  self$classT = ClassTest$new('directTest')
  
},
# UI
ui = function(){
  tagList(
    h3('Call by another class'),
    self$temp$ui(),
    hr(),
    h3('Direct call'),
    self$classT$ui()
  )
},

# server
server = function(input, output, session){
  self$temp$call()
  self$classT$call()
    }
  )
)

app = App$new()

shiny::shinyApp(app$ui(), app$server)

Your code is a bit complicated, but I am sure that you miss to call the server of the child class in the mother class and the child module needs to respect the mothers namespace as well. Here's a working example, which does what it intends to do:

library(shiny)
library(R6)

MyModule <- R6Class(
   "MyModule",
   public = list(id = NULL,
                 initialize = function(id) {
                    self$id <- id
                 }, 
                 ui = function() {
                    ns <- NS(self$id)
                    tagList(h4(self$id), 
                            actionButton(ns("do"), "Calc!"), 
                            verbatimTextOutput(ns("print")))
                 },
                 
                 server = function() {
                    moduleServer(self$id, function(input, output, session) {
                       output$print <- renderPrint({
                          input$do
                          sample(100, 1)
                       })
                    })
                 }
   )
)

MyMotherModule <- R6Class(
   "MyMotherModule",
   public = list(id = NULL,
                 child = NULL,
                 initialize = function(id) {
                    self$id <- id
                    self$child <- MyModule$new(NS(id)("child"))
                 },
                 ui = function() {
                    self$child$ui()
                 },
                 server = function() {
                    self$child$server()
                 }
   )
)

App <- R6Class(
   "App",
   public = list(child1 = NULL,
                 child2 = NULL,
                 mother = NULL,
                 initialize = function() {
                    self$child1 <- MyModule$new("child1")
                    self$child2 <- MyModule$new("child2")
                    self$mother <- MyMotherModule$new("mother1")
                 },
                 ui = function() {
                    fluidPage(
                       fluidRow(
                          self$child1$ui(),
                          self$child2$ui(),
                          self$mother$ui()
                       )
                    )
                 },
                 server = function() {
                    function(input, output, session) {
                       self$child1$server()
                       self$child2$server()
                       self$mother$server()
                    }
                 }
   )
)

app <- App$new()

shinyApp(app$ui(), app$server())

Some Remarks

  1. The id of the child module in the mother module must be also namespaced MyModule$new(NS(id)("child")) to comply with the idea that a name must be only unique within the module and not overall. If I did not namespace child in my example, a child element on top level would mess up with that. (Technically, it would work w/o the namespacing, but then your users will get weird results if you happen to use a top level element with the name of your child element).
  2. A module works (ie the server logic kicks in) only if the respective server function is called. Thus, you need to add self$child$server() in your mother module to "swicth on" the child's server logic.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM