rwhit
rwhit

Reputation: 77

How to use R Shiny moduleServer with multiple outputs

Good afternoon!

I'm trying to create a module, to be used in a shiny app, that will output both a graph and datatable that the user can interact with, the idea being that the user can create various scenarios that will then be evaluated/analysed. What I cannot wrap my head around is how to use the output of the module in the server code. I'm clearly doing something stupid but after two days I've run out of options and articles to read. This is my first exploration into modules.

I'm trying to reproduce this article (although I would prefer to show both input methods at once rather then use a button to swap between the two)
https://www.r-bloggers.com/2021/09/how-to-use-shinymatrix-and-plotly-graphs-as-inputs-in-a-shiny-app/
using DT::datatable similar to this
https://www.r-bloggers.com/2019/04/edit-datatables-in-r-shiny-app/

Here's a reprex of where I've got to, which isn't far (falling at the first hurdle!)... I'm trying to reuse the data produced by the module by rendering it and proving that I can use the data separately.

library(shiny)
library(tidyverse)
library(plotly)
library(DT)
library(shinydashboard)

#base data----
default_df <- structure(list(`0` = 70, `1` = 70, `2` = 70, `3` = 70, `4` = 70, 
                             `5` = 70, `6` = 70, `7` = 70, `8` = 70, `9` = 70, `10` = 70, 
                             `11` = 70, `12` = 70, `13` = 70, `14` = 70, `15` = 70, `16` = 70, 
                             `17` = 70, `18` = 70, `19` = 70, `20` = 70, `21` = 70, `22` = 70, 
                             `23` = 70), row.names = "calls", class = "data.frame")

#module----
##mod server----
mod_editable_srv <- function(id, data) {
    moduleServer(id, function(input, output, session) {
        x = data
        
        proxy = dataTableProxy('x1')
        
        observeEvent(input$x1_cell_edit, {
            info = input$x1_cell_edit
            str(info)
            i = info$row
            j = info$col
            v = info$value
            x[i, j] <<- DT::coerceValue(v, x[i, j])
            replaceData(proxy, x, resetPaging = FALSE)  # important
            print(x)
        })
        
        output$x1 = renderDT(x, selection = 'none', editable = TRUE)
        
        return(x)
    })
}

##mod ui----
mod_editable_ui <- function(id) {
    tagList(
        DT::dataTableOutput(NS(id, "x1")),
        NS(id,"x")
    )
}

#ui----
header <- dashboardHeader(title = "Density based clustering using file upload")

table <- column(width = 4,
                box(width = 12, title = "Input hours",
                    mod_editable_ui('x1')
                ),
                box(width = 12, title = "Input hours",
                    renderDT('test_dat')
                )
)

body <- dashboardBody(table)

ui <- dashboardPage(
    header,
    dashboardSidebar(disable = TRUE),
    body
)

#server----
server <- function(input, output) {

    datafile <- mod_editable_srv("x1", default_df)
    
    output$test_dat <- renderDataTable({
        datafile()$x
        })
}

#run app----
shinyApp(ui = ui, server = server)

I can see that the module is working(ish) by using the print debugging.

There seems to be a lot of tutorials around callModule patterns, but not much around moduleServer patterns. What there is also tend to leave out the server side of things, explaining how modules can move data between each other. As RStudio are not suggesting to use moduleServer I'd prefer to learn these patterns.

Any help would be greatly appreciated!

Upvotes: 0

Views: 1689

Answers (1)

thothal
thothal

Reputation: 20329

Here's a try to explain how to use the return value(s) of modules in the main app. In this toy example, the main app defines a global numeric y which is passed down to the modules. The module in turn defines another numeric x and return the sum and the product respectively.

library(shiny)
rows <- 1:2

sample_ui <- function(id, idx = 1) {
  ns <- NS(id)
  fluidRow(numericInput(ns("x"), paste0("x_", idx), 10, 1, 100)) 
}
sample_server <- function(id, y) {
   moduleServer(id, function(input, output, session) {
      add <- reactive(input$x + y())
      multiply <- reactive(input$x * y())
      list(add = add, multiply = multiply)
   })
}

ui <- fluidPage(fluidRow(numericInput("y", "y", 1, 1, 10)), 
                lapply(rows, function(i) sample_ui(paste0("x", i), i)), 
                fluidRow(verbatimTextOutput("out")))
server <- function(input, output, session) {
   get_y <- reactive(req(input$y))
   hdl <- lapply(paste0("x", rows), sample_server, get_y)

   output$out <- renderPrint({
      ## this would be normally also a loop, but to illustrate I define it explicitely
      ## lapply(hdl, function(h) c(add = h$add(), mult = h$multiply()))
      list(c(add  = hdl[[1]]$add(),
             mult = hdl[[1]]$multiply()),
           c(add  = hdl[[2]]$add(),
             mult = hdl[[2]]$multiply()))
    })
}

shinyApp(ui, server) 

The Idea

  • Your module server simply returns a list of all elements you want to use in the main app (typically reactives).
  • You can also pass in some reactives from the main app to be used in the module if you want.
  • In your main app you simply call the module server and store the result somewhere.
  • This is a list with the reactives as defined before.
  • To use them, you simply "call" them as you would with any other reactive.

Upvotes: 1

Related Questions