Search code examples
rshinyshiny-reactivityshinyjsshinymodules

r shiny - communication of inputId between shiny modules in order to use shinyjs functions like disable/enable/toggle


I've been delving into Shiny modules, and so far, everything has been going well. However, I'm having trouble manipulating inputIds that come from another module. Specifically, I want to use functions like shinyjs::disable to disable a button in one Shiny module by pressing a button in another module (same applies for numericInput, selectizeInput, etc).

I've considered using R6, but it may add unnecessary complexity to the app, given its simplicity. Nonetheless, I'm open to suggestions that involve or do not involve an R6/gargoyle approach.

Here is a toy example that summarises the issue and what I've tried so far.



selectUI <- function(id) {
  ns <- NS(id)
  tagList(
    fluidRow(
      column(width = 4, offset = 0, align = "center",
      selectizeInput(inputId = ns("item"), 
                     label = "selection",
                     choices = c("", "a", "b", "c"), 
                     selected = "")
      ),
      column(width = 8, offset = 0)
    )
  )
}

selectServer <- function(id) {
  moduleServer(
    id,
    function(input, output, session) {
      return (
        list(
          selection = shiny::reactive(input$item)
        )
      )
    }
  )
}

buttonUI <- function(id) {
  ns <- NS(id)
  tagList(
    fluidRow(
      column(width = 2, offset = 0, align = "center",
             circleButton(inputId = ns("btn"))
      ),
      column(width = 10, offset = 0)
    ))
}

buttonServer <- function(id, item) {
  moduleServer(
    id,
    function(input, output, session) {
      observeEvent(
        eventExpr = input$btn, 
        handlerExpr = {
          #' does not work, i've tried with item(), item()$inputId, item$inputId() 
          #' and also hardcoding the id with and without the ns prefix ("item", "select-item", "button-select-item")
          shinyjs::disable("item")  # the issue ----
        },
        ignoreNULL = TRUE,
        ignoreInit = TRUE
      )
      
      #'debug
      observe({
        #' input from module selectUI & selectServer identified correctly
        showNotification(item(), duration = 5) 
      })
    }
  )
}

library(shiny)
library(shinyjs)

ui <- fluidPage(
  useShinyjs(),
  selectUI(id = "select"),
  buttonUI(id = "button"),
)

server <- function(input, output, session) {
  
  item <- selectServer(id = "select")
  buttonServer(id = "button", item = item$selection)

}

shinyApp(ui, server)

Any comments or suggestions are welcome.


Solution

  • Here is a solution that does use gargoyle, because it is such a simple solution that will work generally. The gargoyle package is just a nice wrapper around the use of reactiveVal's inside the session$userData object.

    The alternative would be to pass reactives from one module to another. That will work fine too but becomes a problem when you want to manage multiple 'update signals' like you are doing here (i.e. disable/enable parts of the UI when something happens in a module, somewhere).

    selectUI <- function(id) {
      ns <- NS(id)
      tagList(
        fluidRow(
          column(width = 4, offset = 0, align = "center",
                 selectizeInput(inputId = ns("item"), 
                                label = "selection",
                                choices = c("", "a", "b", "c"), 
                                selected = "")
          ),
          column(width = 8, offset = 0)
        )
      )
    }
    
    selectServer <- function(id) {
      moduleServer(
        id,
        function(input, output, session) {
          
          observeEvent(gargoyle::watch("disable_button"), ignoreInit = TRUE, {
            shinyjs::disable("item")
          })
          
          return (
            list(
              selection = shiny::reactive(input$item)
            )
          )
        }
      )
    }
    
    buttonUI <- function(id) {
      ns <- NS(id)
      tagList(
        fluidRow(
          column(width = 2, offset = 0, align = "center",
                 circleButton(inputId = ns("btn"))
          ),
          column(width = 10, offset = 0)
        ))
    }
    
    buttonServer <- function(id, item) {
      moduleServer(
        id,
        function(input, output, session) {
          observeEvent(input$btn, {
            gargoyle::trigger("disable_button")
          })
          
          #'debug
          observe({
            #' input from module selectUI & selectServer identified correctly
            showNotification(item(), duration = 5) 
          })
          
        }
      )
    }
    
    library(shiny)
    library(shinyjs)
    
    ui <- fluidPage(
      useShinyjs(),
      
      selectUI(id = "select"),
      buttonUI(id = "button"),
    )
    
    server <- function(input, output, session) {
      
      gargoyle::init("disable_button")
      item <- selectServer(id = "select")
      buttonServer(id = "button", item = item$selection)
      
    }
    
    shinyApp(ui, server)