rshinyshinymodules

How to make R Shiny namespace module work more like a reusable function?


I have a large App that uses namespace modules. I have a module that could be used in many other modules, but with different configurations depending on where it is deployed. It's almost like I want this module to also work like a function. In the below MWE module example, in the base utilization of the module the numeric input is provided in-line with an action button which is fine, and in other modules the numeric input is instead provided in the sidebar panel in which case I would like the numeric input automatically removed from the main page with the numeric input hooked in from the sidebar panel. Any ideas how to cleanly and professionally do this? As illustrated below.

On the left is base rendering of the module, useful in this format in several modules; on the right is alternative rendering when the numeric input object input$periods is rendered in the side bar panel: enter image description here

This is how the code below currently renders showing the 2 numeric inputs (I would never want it rendered like this, input$periods either goes in the sidebar panel or on the main page in-line with the other action buttons):

enter image description here

Commenting out from the dummy parent App as shown here gives the base rendering shown above on the left: enter image description here

Here is the MWE code:

library(shiny)

mod_curve_ui <- function(id) {
  ns <- NS(id)
  tagList(
    div(style = "display: flex; align-items: center; gap: 15px;",
        numericInput(ns("periods"), "Periods (x-axis)", value = 10),
        actionButton(ns("y_axis_bttn"), "Y-axis + 1")
    ),
    plotOutput(ns("p"))
  )
}

mod_curve_server <- function(id) {
  moduleServer(id, function(input, output, session) {
    y_val <- reactiveVal(10)
    
    observeEvent(input$y_axis_bttn, { y_val(y_val() + 1) })
    
    output$p <- renderPlot({
      x_vals <- 1:input$periods
      y_vals <- rep(y_val(), length(x_vals))
      plot(x_vals, y_vals, type = "b", ylim = c(0, 20))
    })
  })
}

# Dummy parent App
ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      numericInput("periods", "Periods (x-axis)", value = 10)
    ),
    mainPanel(
      mod_curve_ui("example")
    )
  )
)

server <- function(input, output, session) {
  mod_curve_server("example")
}

shinyApp(ui, server)

Solution

  • Modules are a pair of functions. We can create according parameters.

    I assume the sidebarLayout should not be part of the module:

    library(shiny)
    
    mod_curve_ui <- function(id, sidebar = FALSE) {
      ns <- NS(id)
      tagList(
        div(style = "display: flex; align-items: center; gap: 15px;",
            if(!sidebar){numericInput(ns("periods"), "Periods (x-axis)", value = 10)},
            actionButton(ns("y_axis_bttn"), "Y-axis + 1")
        ),
        plotOutput(ns("p"))
      )
    }
    
    mod_curve_server <- function(id, sidebar = FALSE, sidebar_periods = NULL) {
      moduleServer(id, function(input, output, session) {
    
        y_val <- reactiveVal(10)
        
        observeEvent(input$y_axis_bttn, { y_val(y_val() + 1) })
        
        output$p <- renderPlot({
          if(sidebar){
            x_vals <- 1:sidebar_periods()
          } else {
            x_vals <- 1:input$periods
          }
          y_vals <- rep(y_val(), length(x_vals))
          plot(x_vals, y_vals, type = "b", ylim = c(0, 20))
        })
      })
    }
    
    # Dummy parent App
    ui <- fluidPage(
      sidebarLayout(
        sidebarPanel(
          numericInput("sidebar_periods", "Periods (x-axis)", value = 10)
        ),
        mainPanel(
          mod_curve_ui("sidebar_example", sidebar = TRUE),
          mod_curve_ui("example")
        )
      )
    )
    
    server <- function(input, output, session) {
      mod_curve_server("sidebar_example", sidebar = TRUE, sidebar_periods = reactive(input$sidebar_periods))
      mod_curve_server("example")
    }
    
    shinyApp(ui, server)
    

    PS: this could be combined with your earlier question here.