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

350 Views Asked by At

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)
1

There are 1 best solutions below

0
On BEST ANSWER

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 (i.e. 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.