Only update pickerInput on close

I'd like to have my pickerInput only update on close. Apparently, this is an already-requested feature with a SO solution.

The issue I'm having is integrating that solution with my existing module structure. My current app structure has a module that updates the next picker in a hierarchy when the picker above it is changed.

I tried adding a second condition for my observeEvent in the moduleController as well as adding another observeEvent condition to the server, neither of which worked.

EDITED Working app:


# this solves everything unless I want to have the reactive query to take inputs from user

library(shiny)
library(dplyr)
library(shinyWidgets)
library(plotly)

# module UI

moduleUI <- function(id, label, choices = NULL) {
  ns <- NS(id)
  tagList(pickerInput(ns("select"), label= label, choices = choices,
                      selected = choices, multiple = TRUE, 
                      options = list(`actions-box` = TRUE, `live-search`=TRUE)))
}

# module server root

moduleRootController <- function(id) {
  
  moduleServer(id, function(input, output, session) {
    
    return(reactive({input$select}))
    
  })
}

# module server

moduleController <- function(id, data, selector, input_val, output_val) {
  
  moduleServer(id, function(input, output, session) {
    
    observeEvent(selector(), {
      choices=data %>%
        filter({{input_val}} %in% selector()) %>%
        distinct({{output_val}}) %>%
        arrange({{output_val}}) %>%
        pull({{output_val}})
      updatePickerInput(session, "select", choices = choices, selected = choices)
    }, ignoreNULL = FALSE)
    
    return(reactive({input$select}))
    
  })
}

ui_heirarchy <- function(id){
  ns <- NS(id)
  tagList(moduleUI(ns("ModuleRoot"), label = "Root Label", choices=c("A", "B", "C", "D")),
          moduleUI(ns("Module1"), label = "Test Label 1"),
          moduleUI(ns("Module2"), label = "Test Label 2"),
          moduleUI(ns("Module3"), label = "Test Label 3"))
}

server_heirarchy <- function(id, data) {
  
  moduleServer(id, function(input, output, session) {
    
    mod0 <- moduleRootController("ModuleRoot")
    mod1 <- moduleController("Module1", data, reactive({mod0()}), level1, level2)
    mod2 <- moduleController("Module2", data, reactive({mod1()}), level2, level3)
    mod3 <- moduleController("Module3", data, reactive({mod2()}), level3, level4)
    
    return(list(mod0 = mod0, mod1 = mod1, mod2 = mod2, mod3 = mod3))
    
  })
}

# ui / server / app

ui <- fixedPage(
  ui_heirarchy("heirarchy"),
  plotlyOutput("plot")
)

server <- function(input, output, session) {
  
  x <- tibble(level1 = c(rep("A", 100), rep("B", 100), rep("C", 100), rep("D", 100)),
              level2 = c(rep("A1", 50), rep("A2", 50), rep("B1", 50), rep("B2", 50),
                         rep("C1", 50), rep("C2", 50), rep("D1", 50), rep("D2", 50)),
              level3 = c(rep("A21", 25), rep("A22", 25), rep("A23", 25), rep("A24", 25),
                         rep("B21", 25), rep("B22", 25), rep("B23", 25), rep("B24", 25),
                         rep("C21", 25), rep("C22", 25), rep("C23", 25), rep("C24", 25),
                         rep("D21", 25), rep("D22", 25), rep("D23", 25), rep("D24", 25)),
              level4 = c(rep("A31", 10), rep("A32", 10), rep("A33", 10), rep("A34", 10), rep("A35", 10),
                         rep("A36", 10), rep("A37", 10), rep("A38", 10), rep("A39", 10), rep("A310", 10),
                         rep("B31", 10), rep("B32", 10), rep("B33", 10), rep("B34", 10), rep("B35", 10),
                         rep("B36", 10), rep("B37", 10), rep("B38", 10), rep("B39", 10), rep("B310", 10),
                         rep("C31", 10), rep("C32", 10), rep("C33", 10), rep("C34", 10), rep("C35", 10),
                         rep("C36", 10), rep("C37", 10), rep("C38", 10), rep("C39", 10), rep("C310", 10),
                         rep("D31", 10), rep("D32", 10), rep("D33", 10), rep("D34", 10), rep("D35", 10),
                         rep("D36", 10), rep("D37", 10), rep("D38", 10), rep("D39", 10), rep("D310", 10))) %>%
    mutate(value = runif(400, 0, 100))
  
  out <- server_heirarchy("heirarchy", x)
  
  output$plot <- renderPlotly({
    
    req(out$mod3())
    
    x %>%
      filter(level1 %in% out$mod0()) %>%
      filter(level2 %in% out$mod1()) %>%
      filter(level3 %in% out$mod2()) %>%
      filter(level4 %in% out$mod3()) %>%
      plot_ly(x = ~value, type = 'histogram') %>%
      layout(title = 'A Figure Displaying Itself',
             plot_bgcolor='#e5ecf6', 
             xaxis = list( 
               zerolinecolor = '#ffff', 
               zerolinewidth = 2, 
               gridcolor = 'ffff'), 
             yaxis = list( 
               zerolinecolor = '#ffff', 
               zerolinewidth = 2, 
               gridcolor = 'ffff'))
    })
}

shinyApp(ui, server)
# Libraries and Mock Data

library(dplyr)
library(shiny)
library(shinyWidgets)

x <- tibble(level1 = c(rep("A", 100), rep("B", 100), rep("C", 100), rep("D", 100)),
            level2 = c(rep("A1", 50), rep("A2", 50), rep("B1", 50), rep("B2", 50),
                       rep("C1", 50), rep("C2", 50), rep("D1", 50), rep("D2", 50)),
            level3 = c(rep("A21", 25), rep("A22", 25), rep("A23", 25), rep("A24", 25),
                       rep("B21", 25), rep("B22", 25), rep("B23", 25), rep("B24", 25),
                       rep("C21", 25), rep("C22", 25), rep("C23", 25), rep("C24", 25),
                       rep("D21", 25), rep("D22", 25), rep("D23", 25), rep("D24", 25)),
            level4 = c(rep("A31", 10), rep("A32", 10), rep("A33", 10), rep("A34", 10), rep("A35", 10),
                       rep("A36", 10), rep("A37", 10), rep("A38", 10), rep("A39", 10), rep("A310", 10),
                       rep("B31", 10), rep("B32", 10), rep("B33", 10), rep("B34", 10), rep("B35", 10),
                       rep("B36", 10), rep("B37", 10), rep("B38", 10), rep("B39", 10), rep("B310", 10),
                       rep("C31", 10), rep("C32", 10), rep("C33", 10), rep("C34", 10), rep("C35", 10),
                       rep("C36", 10), rep("C37", 10), rep("C38", 10), rep("C39", 10), rep("C310", 10),
                       rep("D31", 10), rep("D32", 10), rep("D33", 10), rep("D34", 10), rep("D35", 10),
                       rep("D36", 10), rep("D37", 10), rep("D38", 10), rep("D39", 10), rep("D310", 10)))

# Modules

moduleUI <- function(id, label) {
  ns <- NS(id)
  tagList(pickerInput(ns("select"), label= label, choices=c(), multiple = TRUE, options = list(`actions-box` = TRUE, `live-search`=TRUE)))
}

moduleController <- function(input, output, session, choiceLists, selector) {
  ns <- session$ns
  
  observeEvent(selector(), {
    choices <- choiceLists %>%
      filter(level1 %in% selector()) %>%
      distinct(level2) %>%
      arrange(level2) %>%
      pull(level2)
    updatePickerInput(session, "select", choices=choices, selected = choices)
  },ignoreNULL = FALSE)
  
  return(reactive({input$select}))
}

# ui / server / app

ui <- fixedPage(
  pickerInput("module1Mode", label="Set Label", choices=c("A", "B", "C", "D"), selected = c("A", "B", "C", "D"), multiple = TRUE, options = list(`actions-box` = TRUE, `live-search`=TRUE)),
  moduleUI("Module1", label = "Test Label"),
  textOutput("mod1Text"),
)

server <- function(input, output, session) {
  
  
  mmval <- reactiveVal(NULL)
  
  observeEvent(input$module1Mode_open,{
               if(!isTruthy( input$module1Mode_open)){
                 mmval(input$module1Mode)
                 print(paste("Now set to ",paste(input$module1Mode,collapse=";")))
                 }},
               ignoreNULL = FALSE,
               ignoreInit = FALSE)
  
  mod1 <- callModule(moduleController, "Module1", x, mmval)
  
  output$mod1Text <- renderText({
    paste("Test Label selection is", paste(mod1(), collapse=","))
  })
}

shinyApp(ui, server)

This looks promising! Is there a way to make it so the second picker only updates on close as well? Potentially a solution that can scale so if I have a third or fourth picker (generated using the module) they only update on close as well?

yes, the way is to have it check if its _open or not and have that effect its return.

It makes sense in concept, I think the issue I'm having is being able to
1- Reference the input$ values that result from the modules since they are not explicitly defined
2- Reference the inputs in a way that I could add a 5th, 6th, 12th, etc. picker to a hierarchy list without having to manually add those inputs into a list.

I've edited the app above to a more appropriate setup, where all pickerInput IDs are generated by moduleController and moduleRootController.

I have an idea around subsetting the input list to only look at -select elements, and have the observeEvent applied to that result, since I want ALL pickers + outputs to only update on close when ANY picker is changed and closed.

This topic was automatically closed 54 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.