3

I have a selectizeInput with some grouped elements with multiple selection. Is there an elegant way (e.g. using the options argument) of allowing just one element per group, so that a whole group will discarded (or disabled) when an element of this specific group is selected?

So far I tried it programmatically, but than the dropdown menu of the selectizeInput will be closed when updating the selectizeInput.

Minimal example:

library(shiny)

ui <- fluidPage(
    selectizeInput("selInput", "Default",
                   choices=list(g1 = c(A="A",B="B"), 
                                g2 = c(C="C",D="D")),
                   multiple=T),
    
    selectizeInput("oneElementPerGroup", "One element per group",
                   choices=list(g1 = c(A="A",B="B"), 
                                g2 = c(C="C",D="D")),
                   multiple=T)
)

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

    #Removes the corresponding groups of selected items
    observeEvent(input$oneElementPerGroup, ignoreNULL = F, {
        plusChoice <- input$oneElementPerGroup
        names(plusChoice) <- input$oneElementPerGroup
        
        choices <- list(g1 = c(A="A",B="B"), 
                        g2 = c(C="C",D="D"))
        
        if(any(input$oneElementPerGroup %in% c("A", "B"))){
            choices[["g1"]] <- NULL
        }
        if(any(input$oneElementPerGroup %in% c("C", "D"))){
            choices[["g2"]] <- NULL
        }
        choices$we <- plusChoice
        updateSelectizeInput(session,"oneElementPerGroup", 
                             choices = choices,
                             selected=input$oneElementPerGroup)
    })

}

shinyApp(ui = ui, server = server)

Ronak Shah
  • 377,200
  • 20
  • 156
  • 213
wati
  • 310
  • 2
  • 11

1 Answers1

3

You can use pickerInput from {shinyWidgets}. Then we can add a little javascript to do what you want. No server code is needed, very simple. Read more about the data-max-options option: https://developer.snapappointments.com/bootstrap-select/options/.

We need to add the limit to each group, not an overall limit, so we can't add it through the options argument in pickerInput, have to do it in raw HTML or use some js code to inject like what I do.

Be sure your inputId="pick" matches the id in the script #pick. Rename pick to whatever you want.

ui <- fluidPage(
    shinyWidgets::pickerInput(
        inputId = "pick", label = "Selected",
        choices =list(g1 = c(A="A",B="B"), g2 = c(C="C",D="D")),
        multiple = TRUE
        ),
    tags$script(
        '
        $(function(){
            $("#pick optgroup").attr("data-max-options", "1");
        })
        '
    )
)

server <- function(input, output, session){}

shinyApp(ui, server)

updates:

If you need to update, we need to run the script again but from server. We can send js by using {shinyjs}. Imagine an observer triggers the update event.

library(shinyjs)
ui <- fluidPage(
    useShinyjs(),
    shinyWidgets::pickerInput(
        inputId = "pick", label = "Selected",
        choices =NULL,
        multiple = TRUE
    )
)

server <- function(input, output, session){
    observe({
        shinyWidgets::updatePickerInput(session, "pick", choices = list(g1 = c(A="A",B="B"), g2 = c(C="C",D="D")))
        observeEvent(once = TRUE, reactiveValuesToList(session$input), {
            runjs('$("#pick optgroup").attr("data-max-options", "1");')
        }, ignoreInit = TRUE)
    })

    
}

shinyApp(ui, server)

lz100
  • 6,990
  • 6
  • 29
  • Unfortunately it doesn't work when I update the pickerInput. Do I have to call the script again somewhere/somehow? Btw the 'disabled' option for the choicesOpt argument is also helpful. – wati Aug 26 '21 at 10:25