Ordering Pre-selected Items in selectizeInput

SelectizeInput

SelectizeInput is a powerful shiny widget built on the selectize.js library. The behavior of selectize.js can be extended using plugins, which are available in the shiny::selectizeInput() functon. One particularly useful plugin is "drag_drop" which allows a user to re-order the items selected in a selectizeInput. When paired with the "remove_button" plugin this makes a powerful UI in which a user may select, reorder and remove items from a list.
When building a selectizeInput widget, we can list items that have been pre-selected, which allows us to start in a specific state. With drag-drop and remove_button added, these pre-selected items will appear in the selectizeInput box, already selected.

For example, the code below produces the following output:

1shiny::selectizeInput(inputId = "sel", 
2                      label = "Selection", 
3                      choices = 1:10, 
4                      selected = 1:5, 
5                          multiple = TRUE, 
6                          options = list(
7                            plugins= list("remove_button", "drag_drop")
8                          ))

There is one caveat in this approach which is that the order of selected items is not honored. For example, the code below produces the following output:

1shiny::selectizeInput(inputId = "sel", 
2                      label = "Selection", 
3                      choices = 1:10, 
4                      selected = c(4, 7, 2, 3), 
5                          multiple = TRUE, 
6                          options = list(
7                            plugins= list("remove_button", "drag_drop")
8                          ))

The correct items are selected but their order has not been preserved. Instead, they are ordered according to the order of the choices parameter. This is normal behavior and is an artefact in the way that selectize.js works.

Solution to Order of Pre-selected Items

A solution to this issue lies in re-ordering the choices parameter. The following shiny app will also fail to maintain the order of the selected items:

 1library(shiny)
 2
 3vals <- setNames(1:10, sapply(1:10, function(i) paste("Row", i)))
 4vals_start <- c(4, 7, 2, 3)
 5
 6ui <- fluidPage(
 7  fluidRow(
 8    column(6, uiOutput("ui_selectize")),
 9    column(6, verbatimTextOutput("ui_selected_values"))
10  )
11)
12
13server <- function(input, output, session) {
14  
15  output$ui_selectize <- renderUI({
16    shiny::selectizeInput(inputId = "sel", 
17                          label = "Selection", 
18                          choices = vals, 
19                          selected = vals_start, 
20                          multiple = TRUE, 
21                          options = list(
22                            plugins= list("remove_button", "drag_drop")
23                          ))
24  })
25  
26  output$ui_selected_values <- renderPrint({
27    input$sel
28  })
29  
30}
31
32shinyApp(ui, server)

But, with the addition of a line at the start of output$ui_selectize, we maintain the order:

 1library(shiny)
 2
 3vals <- setNames(1:10, sapply(1:10, function(i) paste("Row", i)))
 4vals_start <- c(4, 7, 2, 3)
 5
 6ui <- fluidPage(
 7  fluidRow(
 8    column(6, uiOutput("ui_selectize")),
 9    column(6, verbatimTextOutput("ui_selected_values"))
10  )
11)
12
13server <- function(input, output, session) {
14  
15  output$ui_selectize <- renderUI({
16    vals <- c(vals[match(vals_start, vals)], vals[which(!vals %in% vals_start)])
17    shiny::selectizeInput(inputId = "sel", 
18                          label = "Selection", 
19                          choices = vals, 
20                          selected = vals_start, 
21                          multiple = TRUE, 
22                          options = list(
23                            plugins= list("remove_button", "drag_drop")
24                          ))
25  })
26  
27  output$ui_selected_values <- renderPrint({
28    input$sel
29  })
30  
31}
32
33shinyApp(ui, server)

The additional line vals <- c(vals[match(vals_start, vals)], vals[which(!vals %in% vals_start)]) simply reorders vals so that our selected items come first in their correct order and the other items follow.

 1vals <- setNames(1:10, sapply(1:10, function(i) paste("Row", i)))
 2print(vals)
 3#Row 1  Row 2  Row 3  Row 4  Row 5  Row 6  Row 7  Row 8  Row 9 Row 10 
 4#    1      2      3      4      5      6      7      8      9     10 
 5
 6vals_start <- c(4, 7, 2, 3)
 7vals <- c(vals[match(vals_start, vals)], vals[which(!vals %in% vals_start)])
 8print(vals)
 9#Row 4  Row 7  Row 2  Row 3  Row 1  Row 5  Row 6  Row 8  Row 9 Row 10 
10#    4      7      2      3      1      5      6      8      9     10 

The shiny output now looks like this. The order of our pre-selected items is honored.