Acceda a los valores devueltos de módulos Shiny creados dinámicamente

Aug 18 2020

Estoy buscando construir una aplicación brillante que cree módulos dinámicamente (a través de callmodule) que devuelva un formulario simple. Tengo 2 cabos sueltos sobre los que agradecería alguna orientación, por favor.

En primer lugar, cuando se traen varios formularios al usuario (mediante un clic en un botón), los valores de los formularios renderizados anteriormente vuelven a los valores predeterminados. ¿Cómo detengo este comportamiento para que los valores permanezcan en la selección de los usuarios?

Y 2, ¿cómo accedo y presento 'todos' los valores de las selecciones en un solo tibble que se puede mostrar en un tableOutput? He puesto un ejemplo simple a continuación usando observeEvent; También probé una variación con eventReactive, sin embargo, parece que no puedo acceder a las salidas del módulo de llamada.

¡Gracias de antemano!

library(shiny)
library(stringr)


gen_r_8_formUI <- function(id){
  
  ns <- NS(id)
  
  tagList(fluidRow(column(width = 4, selectInput(ns("slt_forename"), 'forename', choices = unique(c("john", "paul", "george", "ringo")))),
          column(width = 4, selectInput(ns("slt_surname") , 'surname' , choices = unique(c("lennon", "mccartney", "harrison", "starr"))))))
}

gen_r_8_form <- function(input, output, session){

  select_values <- reactiveValues(forename = NULL, surname = NULL)  
  observeEvent(input$slt_forename,{select_values$forename <- input$slt_forename}) observeEvent(input$slt_surname, {select_values$surname <- input$slt_surname})
  select_values_all <- reactive({tibble(forename  = select_values$forename, surname = select_values$surname)})
  
  return(list(select_values_all = reactive({select_values_all()})))
}


ui <- fluidPage(
  column(width = 2, actionButton("btn_gen_r_8_form", "GEN R 8 a FORM")),
  column(width = 6, uiOutput("all_ui_forms")),
  column(width = 4, tableOutput("all_form_values_table")))

server <- function(input, output) {
  
  rctv_uis                     <- reactiveValues(all_ui          = list())
  gen_forms                    <- reactiveValues(all_form_values = list())
  output$all_ui_forms <- renderUI({tagList(rctv_uis$all_ui)})
  output$all_form_values_table <- renderTable({all_form_values_rctv()}) observeEvent(input$btn_gen_r_8_form, {
    
    x_id  <- paste( "ns_", str_replace_all(paste(Sys.time()), "-| |:", '') , sep = '')
    gen_forms$all_form_values[[x_id]] <- callModule(module = gen_r_8_form, id = x_id) rctv_uis$all_ui[[x_id]] <- gen_r_8_formUI(id = x_id)

  })
  
  
  all_form_values_rctv <- reactive({
    
    # Question - how to make a tibble with all form values?
    
    # tibble(
    #   forenames = 'all gen_forms$all_form_values forenames', # surnames = 'all gen_forms$all_form_values surnames'
    # )
    
  })
}

shinyApp(ui = ui, server = server)

Respuestas

1 starja Aug 18 2020 at 04:37

Aquí hay una solución que usa insertUI. Tiene la ventaja de que los elementos de la interfaz de usuario existentes permanecen iguales (no se restablecen los módulos anteriores) y solo se agregan módulos nuevos. Para determinar dónde se agrega la interfaz de usuario, defina a tags$div(id = "tag_that_determines_the_position")en la UIfunción. Entonces, insertUItoma esto como argumento. Además, he cambiado algunas cosas:

  • simplificó el código para la función del servidor del módulo, básicamente solo necesita un reactive
  • uso de la nueva interfaz del módulo introducido con shiny 1.5.0
  • use una estructura de datos reactiva un poco más simple (menos anidamiento)
library(shiny)
library(stringr)


gen_r_8_formUI <- function(id){
  
  ns <- NS(id)
  
  tagList(fluidRow(column(width = 4, selectInput(ns("slt_forename"), 'forename', choices = unique(c("john", "paul", "george", "ringo")))),
                   column(width = 4, selectInput(ns("slt_surname") , 'surname' , choices = unique(c("lennon", "mccartney", "harrison", "starr"))))))
}

gen_r_8_form <- function(id){
  moduleServer(
    id,
    function(input, output, session) {
      select_values_all <- reactive({tibble(forename  = input$slt_forename,
                                            surname  = input$slt_surname)}) return(list(select_values_all = reactive({select_values_all()}))) } ) } ui <- fluidPage( column(width = 2, actionButton("btn_gen_r_8_form", "GEN R 8 a FORM")), column(width = 6, tags$div(id = "add_UI_here")),
  column(width = 4, tableOutput("all_form_values_table")))

server <- function(input, output) {
  gen_forms                    <- reactiveValues()
  current_id <- 1
  
  observeEvent(input$btn_gen_r_8_form, { x_id <- paste0("module_", current_id) gen_forms[[x_id]] <- gen_r_8_form(id = x_id) insertUI(selector = "#add_UI_here", ui = gen_r_8_formUI(x_id)) current_id <<- current_id + 1 }) all_form_values_rctv <- reactive({ res <- lapply(reactiveValuesToList(gen_forms), function(current_module_output) { current_module_output$select_values_all()
    })
    
    # prevent to show an error message when the first module is added
    if (length(res) != 0 && !is.null(res[[1]]$forename)) { dplyr::bind_rows(res) } else { NULL } }) output$all_form_values_table <- renderTable({
    all_form_values_rctv()
  })
}

shinyApp(ui = ui, server = server)
MrFlick Aug 18 2020 at 03:56

Creo que quieres algo como esto

  all_form_values_rctv <- reactive({
    dplyr::bind_rows(lapply(gen_forms$all_form_values, function(x) { x$select_values_all()
    }))
  })

Ha recopilado todos los elementos reactivos del modelo, por gen_forms$all_form_valueslo que los itera y obtiene el valor reactivo y luego une todas esas tablas.