Acceda a los valores devueltos de módulos Shiny creados dinámicamente
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
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 UI
función. Entonces, insertUI
toma 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)
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_values
lo que los itera y obtiene el valor reactivo y luego une todas esas tablas.