18

I want to use a modal window inside a Shiny module. The user interacts with the modal window, the module processes the user's input.

In this minimal example the module is supposed to remove the modal when the user clicks the "close modal" button:

library(shiny)

# Modal module UI
modalModuleUI <- function(id) {
  ns <- NS(id)
  actionButton(ns("openModalBtn"), "Open Modal")
}

# Modal module server
modalModule <- function(input, output, session) {

  myModal <- function() {
    modalDialog(
      actionButton("closeModalBtn", "Close Modal")
    )
  }
  # Show modal dialog on start up
  observeEvent(input$openModalBtn,
               ignoreNULL = FALSE,
               showModal(myModal())
               )

  # close modal on button click (not working)
  observeEvent(input$closeModalBtn, { 
    removeModal() 
  })
}

# Main app UI
ui <- fluidPage(modalModuleUI("foo"))

# Main app server
server <- function(input, output, session) {
  callModule(modalModule, "foo")
}

shinyApp(ui, server)

However, clicking the "close modal" button does not trigger the observeEvent() in the module server function. I cannot figure out how to access (i.e. observe) the content of the modal window in the module. I guess it is a namespace issue.

Edit: The interactive example now works. See my answer below.

Tobias Hotzenplotz
  • 1,177
  • 1
  • 11
  • 15

2 Answers2

27

I figured it out myself after re-reading this more carefully. Like with renderUI the id elements in the modal need to be wrapped in ns() to make them available in the module namespace. The namespace has to be loaded inside the modal explicitly using ns <- session$ns, like this:

library(shiny)

# Modal module UI
modalModuleUI <- function(id) {
  ns <- NS(id)
  actionButton(ns("openModalBtn"), "Open Modal")
}

# Modal module server
modalModule <- function(input, output, session) {

  myModal <- function() {
    ns <- session$ns
    modalDialog(actionButton(ns("closeModalBtn"), "Close Modal"))
  }

  # open modal on button click
  observeEvent(input$openModalBtn,
               ignoreNULL = FALSE,   # Show modal on start up
               showModal(myModal())
  )

  # close modal on button click
  observeEvent(input$closeModalBtn, { 
    removeModal() 
  })
}

# Main app UI
ui <- fluidPage(modalModuleUI("foo"))

# Main app server
server <- function(input, output, session) {
  callModule(modalModule, "foo")
}

shinyApp(ui, server)

Note: If the myModal function is defined outside the module server function, one has to pass session when calling it, i.e. showModal(myModal(session)) and myModal <- function(session) {...}.

I have updated the example app so that it works now and added a textInput too.

Tobias Hotzenplotz
  • 1,177
  • 1
  • 11
  • 15
  • 1
    The only reason to use this framework is if you want closing the modal to trigger some event. Otherwise it is over complicated, see answer below. – jamesguy0121 Nov 06 '19 at 20:49
  • Indeed, that was what I was aiming for. This is just a minimal example. In reality, the modal window has filtering options for the data set that is displayed in the main window. The filters get applied when the user closes the modal. If it's just about closing the modal, then a modal Button would be enough. – Tobias Hotzenplotz Oct 02 '20 at 14:54
3

There is a modalButton() function in shiny designed to do exactly this. You do not need to worry about any namespace issues if you use this. Here is the documentation. And here it is in action.

library(shiny)

# Modal module UI
modalModuleUI <- function(id) {
  ns <- NS(id)
  actionButton(ns("openModalBtn"), "Open Modal")
}

# Modal module server
modalModule <- function(input, output, session) {

  myModal <- function() {
    modalDialog(
      footer = modalButton("Close Modal")
    )
  }
  # Show modal dialog on start up
  observeEvent(input$openModalBtn,
               ignoreNULL = TRUE,
               showModal(myModal())
  )
}

# Main app UI
ui <- fluidPage(modalModuleUI("foo"))

# Main app server
server <- function(input, output, session) {
  callModule(modalModule, "foo")
}

shinyApp(ui, server)
jamesguy0121
  • 1,124
  • 11
  • 28