wati
wati

Reputation: 310

selectizeInput: allowing one element per group

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)

Upvotes: 3

Views: 297

Answers (1)

lz100
lz100

Reputation: 7360

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)

Upvotes: 3

Related Questions