Home > Software design >  How does one retrieve values from a shiny module that are created iteratively based on user input?
How does one retrieve values from a shiny module that are created iteratively based on user input?

Time:09-02

I have built the following module to create a colourpicker widget

library(shiny)
library(glue)
library(colourpicker)
library(scales)

mod_fill_def_ui <- function(id, level, nlevel, n) {
  ns <- NS(id)
  colourInput(inputId = ns("Fill"),
              label = glue("Colour for {level}"),
              value = hue_pal()(nlevel)[n])
}

mod_fill_def_server <- function(id) {
  moduleServer(id,
               function(input, output, session) {
                 reactive(input$Fill)
               }
  )
}

As an example, here's what the basic use of the module looks like:

ui <- fluidPage(
  mainPanel(
    mod_fill_def_ui("fill_example", "Item", 5, 1), 
    verbatimTextOutput("fill_hex")
  )
)

server <- function(input, output, session) {   
  hex <- mod_fill_def_server("fill_example")
  output$fill_hex <- renderPrint(
    hex()
  )
}

shinyApp(ui, server)

enter image description here

Now, I would like to apply this module as many times as needed depending on a user's input. I can apply the UI portion of the module fine as needed with the code below, but I can't figure out how to apply the server section of the module to retrieve each of the HEX codes selected.

Eventually, I plan to turn this into a named vector (i.e. c("Item1" = "#HEXCODE")) so that I can then apply it to ggplot2::scale_*_manual(values = ...).

Animals <- c("Cat", "Dog", "Rabbit", "Horse")
Vehicles <- c("Car", "Motorcycle", "Truck", "Plane", "Boat")

ui <- fluidPage(
  mainPanel(
    selectInput("items", "Item to choose:", c("Animals", "Vehicles"), "Animals"),
    br(),
    uiOutput("levels_fills"),
    verbatimTextOutput("fill_hex")
  )
)

server <- function(input, output, session) {
  vec <- reactive({
    switch(input$items, Animals = Animals, Vehicles = Vehicles)
  })
  
  output$levels_fills <- renderUI(
    purrr::map2(vec(), seq_along(vec()), function(level, n) {
      mod_fill_def_ui(glue("fill_{level}"), level, length(vec()), n)
    })
  )
  
  ### How do I use `mod_fill_def_server()` iteratively to retrieve the hex codes?
}

shinyApp(ui, server)

CodePudding user response:

Here is a very hacky way to do it. If someone comes up with a better solution I'd be very happy! You basically store the unevaluated reactives returned by the module in a reactiveValues object and then evaluate every element of reactiveValues in a new reactive. You could probably also use an ordinary list instead of reactiveValues, but then you would need to use <<- to assign the values inside the observeEvent which can lead to problems.

server <- function(input, output, session) {
  mod_results <- reactiveValues()
  vec <- reactive({
    switch(input$items, Animals = Animals, Vehicles = Vehicles)
  })
  
  output$levels_fills <- renderUI(
    purrr::map2(vec(), seq_along(vec()), function(level, n) {
      mod_fill_def_ui(glue("fill_{level}"), level, length(vec()), n)
    })
  )
  
  ### How do I use `mod_fill_def_server()` iteratively to retrieve the hex codes?
  observeEvent(vec(), {
    purrr::map(vec(), function(level) {
      mod_results[[glue("fill_{level}")]] <- mod_fill_def_server(id = glue("fill_{level}"))
    })
  })
  
  hex <- reactive({
    lapply(reactiveValuesToList(mod_results), function(current_module_output) {
      current_module_output()
    })
  })
  
  output$fill_hex <- renderPrint({
    hex()
  })
}

However, if your module only contains the colourInput, you don't need a module but could use the colourInput directly:

server <- function(input, output, session) {
  vec <- reactive({
    switch(input$items, Animals = Animals, Vehicles = Vehicles)
  })
  
  output$levels_fills <- renderUI(
    purrr::map2(vec(), seq_along(vec()), function(level, n) {
      colourInput(inputId = glue("fill_{level}"),
                  label = glue("Colour for {level}"),
                  value = hue_pal()(length(vec()))[n])
    })
  )
  
  hex <- reactive({
    purrr::map(vec(), function(level) {
      input[[glue("fill_{level}")]]
    })
  })
  
  output$fill_hex <- renderPrint({
    hex()
  })
}
  • Related