0

This is a follow up of this question: How to pass values from ui to server in a shiny module

In the minimum working example below of an R Shiny Module, I can use the function send_value_to_server() to send a value from UI to SERVER.

However, I cannot find a way to send more complex data structures like a dataframe, is there a way to do that?

library(shiny)

module_ui <- function(id, value_to_send) {
    
    ns <- shiny::NS(id)
        
    send_value_to_server <- function(id, value) {
    
        shiny::selectInput(
          inputId = id,
          label = "If you can see this, you forgot shinyjs::useShinyjs()", 
          choices = value, 
          selected = value, 
          multiple=TRUE
        ) |> shinyjs::hidden()
    
    }  
    
    shiny::tagList( 
    
        shinyjs::useShinyjs(),  # Set up shinyjs
        
        send_value_to_server(id = ns("value_to_send"), value = value_to_send),
    
        shiny::verbatimTextOutput(ns("text_out"))
        
    )    
}

module_server <- function(id) {
  moduleServer(id, function(input, output, session) {
  
    value_received <- reactive(input$value_to_send) 
    
    output$text_out <- renderPrint({
        paste("value passed from ui to server:", value_received())
    })  
    
  })
}

ui <- fluidPage(
    module_ui("test", value_to_send = 10)
)  

server <- function(input, output, server) {
    module_server("test")
}
  

shinyApp(ui, server)
GitHunter0
  • 424
  • 6
  • 10
  • From what i see, you are creating a ui with a selectInput that contains a value. And then you want that value returned to the server from the module? In other words you want a module to return for example a dataframe and be able to use it inside the server? – jpdugo17 Jan 11 '22 at 02:33
  • @jpdugo17 , yes, exactly. – GitHunter0 Jan 11 '22 at 14:43

1 Answers1

0

In this example app, you can send a dataframe to the server module, mutate it and display as a table, all inside the module.

library(shiny)
library(tibble)
library(dplyr)

selectUI <- function(id, values) {
    tagList(
        tableOutput(NS(id, 'table'))
        )
    
}

selectServer <- function(id, my_df) {
    moduleServer(id, function(input, output, session) {
        # make some complex calculation with values from the input
        df <- reactive({mutate(my_df(), values_squared = x^2)})
        # now we can make the module return an object (like a regular function), in this case a df.
        output$table <- renderTable({
            df()
        })
    })
}





ui <- fluidPage(
    sidebarLayout(sidebarPanel(
    shiny::selectInput(
        inputId = "values",
        label = "Data for dataframe",
        choices = 1:10,
        selected = 1:5,
        multiple = TRUE
    )),mainPanel(
    selectUI("test", values = 1:10),
    verbatimTextOutput("print")
)))

server <- function(input, output, server) {
    df <- reactive({tibble(x = as.numeric(input$values))})
    selectServer("test", df)
    

}



shinyApp(ui, server)

Also, this way the module can react to changes in the reactive passed as as the argument, while the data passed to the ui will always be static.

Edit: If we really need to do it through the module UI, we can send the df as text and then parse it to construct a data frame. I do not recommend this method mainly for security reasons. (A malicious user can send arbitrary code to the server).

code:

library(shiny)

module_ui <- function(id, value_to_send) {
    
    ns <- shiny::NS(id)
    
    send_value_to_server <- function(id, value) {
        
        shiny::selectInput(
            inputId = id,
            label = "If you can see this, you forgot shinyjs::useShinyjs()", 
            choices = value, 
            selected = value, 
            multiple=TRUE
        ) |> shinyjs::hidden()
        
    }  
    
    shiny::tagList( 
        
        shinyjs::useShinyjs(),  # Set up shinyjs
        
        send_value_to_server(id = ns("value_to_send"), value = value_to_send),
        
        shiny::verbatimTextOutput(ns("text_out"))
        
    )    
}

module_server <- function(id) {
    moduleServer(id, function(input, output, session) {
        
        value_received <- reactive(eval(parse(text = input$value_to_send))) 
        
        output$text_out <- renderPrint({
            value_received()
        })  
        
    })
}

ui <- fluidPage(
    module_ui("test", value_to_send = 'structure(list(Sepal.Length = c(5.1, 4.9, 4.7, 4.6, 5), Sepal.Width = c(3.5, 
                                                                             3, 3.2, 3.1, 3.6), Petal.Length = c(1.4, 1.4, 1.3, 1.5, 1.4), 
                    Petal.Width = c(0.2, 0.2, 0.2, 0.2, 0.2), Species = structure(c(1L, 
                                                                                    1L, 1L, 1L, 1L), .Label = c("setosa", "versicolor", "virginica"
                                                                                    ), class = "factor")), row.names = c(NA, 5L), class = "data.frame")')
)  

server <- function(input, output, server) {
    module_server("test")
}


shinyApp(ui, server)
jpdugo17
  • 6,816
  • 2
  • 11
  • 23
  • Sorry, I believe I misunderstood your question. What I actually need is a dataframe passed in `selectUI(, df=data.frame(...))` to be available in `selectServer()`, so that I can use `df` inside `selectServer()`. – GitHunter0 Jan 11 '22 at 20:49
  • You can pass the dataframe directly to the server module. This is better because it allows the module to react from the df changing in the global server. – jpdugo17 Jan 12 '22 at 03:10
  • Yes, I'm doing that as a workaround, however, since the dataframe must be exactly the same, this manual method is more prone to errors. Also, there are internal dataframes and lists in my moduleUI that I would like to make available in the moduleServer. – GitHunter0 Jan 12 '22 at 15:18
  • Data manipulation should be done inside the server, the ui, apart from being static, should contain html basically. Is there a specific reason you need dataframes inside a UI? – jpdugo17 Jan 12 '22 at 17:21
  • I have a little complex pickerInput() UI which displays extra data from a dataframe, then the server uses updatePickerInput() to update it. Since both use mostly the same data and configuration parameters, it feels clumsy to have to pass them twice, one for pickerInput() and other for updatePickerInput(). Therefore I pass every commom info from pickerInput() in the UI to updatePickerInput() in the server. However, a data structure like a dataframe I could not found a way to do that. – GitHunter0 Jan 12 '22 at 18:23