Search code examples
reactjsshinymodulereactiver6

Dynamic connection of shiny pages on observe event using R6 and call modules


I want to connect two shiny pages coded in separate R6 classes. I am really stuck on how to go about this. Below is a simple working example. When private$..counter == 4 in Page1, I want to hide all contents in Page1 and activate Page2. I know a simple showModal, modal dialog could say "Thanks". I just used a simple example. In reality, this new page would also show more complex content like Page1. Is there any way to achieve what I want using shinyjs? Or other means?

Page 1

library(R6)
library(stringi)
library(shiny)

df <- data.frame(dp = c("dp1", "dp2", "dp3"), desc = c("problem 1", "problem 2", "problem 3"))

page1 <- R6::R6Class(classname = "Page1",
            private = list(
              #unique string id
              ..id = stringi::stri_rand_strings(1, 18),
              #the data to be iterated through
              ..df = df,
              #counter to uqpdate text
              ..counter = 1,
              #initiating the dp and desc
              ..dp = NA,
              ..desc = NA,
              
              #the underlying server, to be created like a normal server
              .server = function(input, output, session){
                
                output$text <- renderText({ 
                  self$desc$text
                })
                
                observeEvent(input$button, {
                  private$..counter <- private$..counter + 1
                  
                  self$update_private()
                  self$desc$text <- private$..desc
                  #check the private content since the print is not updating
                  print(private$..counter)
                  print(private$..dp)
                  print(private$..desc)
                })
              }
            ),
            active = list(
              .counter = function(value){
                if(missing(value)){
                  private$..counter
                }else{
                  private$..counter <- value
                }
              }
            ),
            public = list(
              #create names for ui elements
              button = NULL,
              text = NULL,
              
              
              #Need this to update the text***************
              desc = reactiveValues(text = NA),
              
              initialize = function(counter = self$.counter){
                self$.counter <- counter
                self$button <- self$get_id("button")
                self$text <- self$get_id("text")
                self$update_private()
                self$desc$text <- private$..desc
              },
              
              #gives ui outputs unique names tied to the user's id
              get_id = function(name, ns = NS(NULL)){
                ns <- NS(ns(private$..id))
                id <- ns(name)
                return(id)
              },
              #automatically updates the private field based on the counter
              update_private = function(){
                if(private$..counter == 1){
                  private$..dp <- "dp1"
                } else if(private$..counter == 2){
                  private$..dp <- "dp2"
                } else{
                  private$..dp <- "dp3"
                }
                private$..desc <- private$..df[private$..df$dp == private$..dp, "desc"]
              },
              
              ui = function(){
                fluidPage(
                  h1("An Example"),
                  mainPanel(
                  textOutput(self$text)),
                  sidebarPanel(
                  shiny::actionButton(inputId = self$button, 
                                      label = 'Update!', 
                                      width = '100%'
                  ))
                  

                )
              },#end ui
              
              server = function(input, output, session){
                counter <- reactiveVal(private$..counter)
                callModule(module = private$.server, id = private$..id)
              }
            )
)

Page 2

page2 <- R6::R6Class(classname = "Page2",
                     private = list(
                       ..init = NULL,
                       #unique string id
                       ..id = NULL,
                      
                       #the underlying server, to be created like a normal server
                       .server = function(input, output, session){
                         
                       }
                     ),
                     active = list(
                       .init = function(value){
                         if(missing(value)){
                           message("init class object required")
                         }else{
                           private$..init <- value
                         }
                       }
                       
                     ),
                     public = list(
                       initialize = function(init = self$.init){
                         self$.init <- init
                         private$..id <- private$..init$id
                         
                       },
                       
                       #gives ui outputs unique names tied to the user's id
                       get_id = function(name, ns = NS(NULL)){
                         ns <- NS(ns(private$..id))
                         id <- ns(name)
                         return(id)
                       },
                       
                       ui = function(){
                         fluidPage(
                           h1("An Example Connection"),
                           mainPanel(
                             "Thanks for participating!")
                         )
                       },#end ui
                       
                       server = function(input, output, session){
                         callModule(module = private$.server, id = private$..id)
                       }
                     )
)

App

app1 <- page1$new()
app2 <- page2$new(init = app1)

#*******HELP************
ui <- app1$ui()

server <- function(input, output, session) {
  app1$server()
  app2$server()
}
shinyApp(ui = ui, server = server)

Solution

  • Here is the answer to this question in case someone else runs into these issues. Essentially, using an id and ns you can save the ui elements with divs that will reactively show and hide when a condition is met.

    library(R6)
    library(stringi)
    library(shiny)
    library(shinyjs)
    df <- data.frame(dp = c("dp1", "dp2", "dp3"), desc = c("problem 1", "problem 2", "problem 3"))
    
    page1 <- R6::R6Class(classname = "Page1",
                private = list(
                  #unique string id
                  ..id = NULL,
                  #the data to be iterated through
                  ..df = df,
                  #counter to uqpdate text
                  ..counter = 1,
                  #initiating the dp and desc
                  ..dp = NA,
                  ..desc = NA,
                  
                  #the underlying server, to be created like a normal server
                  .server = function(input, output, session){
                    
                    output$text <- renderText({ 
                      self$desc$text
                    })
                    
                    observeEvent(input$button, {
                      private$..counter <- private$..counter + 1
                      if (private$..counter == 4){
                        print('here')
                        shinyjs::hide(id = 'uip1')
                        shinyjs::show(id = 'uip2')
                        
                      }
                      self$update_private()
                      self$desc$text <- private$..desc
                      #check the private content since the print is not updating
                      print(private$..counter)
                      print(private$..dp)
                      print(private$..desc)
                      
                      
                    })
                  }
                ),
                active = list(
                  .id = function(value){
                    if(missing(value)){
                      private$..id
                    }else{
                      private$..id <- value
                    }
                  },
                  .counter = function(value){
                    if(missing(value)){
                      private$..counter
                    }else{
                      private$..counter <- value
                    }
                  }
                ),
                public = list(
                  #create names for ui elements
                  button = NULL,
                  text = NULL,
                  
                  
                  #Need this to update the text***************
                  desc = reactiveValues(text = NA),
                  
                  initialize = function(
                    id = self$.id,
                    counter = self$.counter){
                    self$.id <- id
                    self$.counter <- counter
                    self$button <- self$get_id("button")
                    self$text <- self$get_id("text")
                    self$update_private()
                    self$desc$text <- private$..desc
                  },
                  
                  #gives ui outputs unique names tied to the user's id
                  get_id = function(name, ns = NS(NULL)){
                    ns <- NS(ns(private$..id))
                    id <- ns(name)
                    return(id)
                  },
                  #automatically updates the private field based on the counter
                  update_private = function(){
                    if(private$..counter == 1){
                      private$..dp <- "dp1"
                    } else if(private$..counter == 2){
                      private$..dp <- "dp2"
                    } else{
                      private$..dp <- "dp3"
                    }
                    private$..desc <- private$..df[private$..df$dp == private$..dp, "desc"]
                  },
                  
                  ui = function(){
                    fluidPage(
                      h1("An Example"),
                      mainPanel(
                      textOutput(self$text)),
                      sidebarPanel(
                      shiny::actionButton(inputId = self$button, 
                                          label = 'Update!', 
                                          width = '100%'
                      ))
                      
    
                    )
                  },#end ui
                  
                  server = function(input, output, session){
                    callModule(module = private$.server, id = private$..id)
                  }
                )
    )
    
    #page 2 - thank you
    
    page2 <- R6::R6Class(classname = "Page2",
                         private = list(
                           #unique string id
                           ..id = NULL,
                          
                           #the underlying server, to be created like a normal server
                           .server = function(input, output, session){
                             
                           }
                         ),
                         active = list(
                           .id = function(value){
                             if(missing(value)){
                               private$..id
                             }else{
                               private$..id <- value
                             }
                           }
                           
                         ),
                         public = list(
                           initialize = function(id = self$.id){
                             self$.id <- id
                           },
                           
                           #gives ui outputs unique names tied to the user's id
                           get_id = function(name, ns = NS(NULL)){
                             ns <- NS(ns(private$..id))
                             id <- ns(name)
                             return(id)
                           },
                           
                           ui = function(){
                             fluidPage(
                               h1("An Example Connection"),
                               mainPanel(
                                 "Thanks for participating!")
                             )
                           },#end ui
                           
                           server = function(input, output, session){
                             callModule(module = private$.server, id = private$..id)
                           }
                         )
    )
    
    get_id <- function(name, id, ns = NS(NULL)){
      ns <- NS(ns(id))
      id <- ns(name)
      return(id)
    }
    
    id <- stringi::stri_rand_strings(1, 18)
    app1 <- page1$new(id = id)
    app2 <- page2$new(id = id)
    
    #NEED: without this, the elements won't react
    uip1 <- get_id("uip1", id)
    uip2 <- get_id("uip2", id)
    
    ui <- shiny::tagList(
      shinyjs::useShinyjs(),
      div(id = uip1,
          style = "display:show;",
          app1$ui()
      ),
      div(id = uip2,
          style = "display:none;",
          app2$ui()
      )
      )
      
      
      
    
    server <- function(input, output, session) {
      app1$server()
      app2$server()
    }
    shinyApp(ui = ui, server = server)