Shiny widget for reordering elements in a vector
On multiple sites, you have a drag and drop interface to reorder items in a list. I'm looking for something similar in Shiny. I want the user to be able to drag the list items to change the priority by changing the order.
I now have a solution that is abusing selectizeInput()
. It works, but it quickly becomes cumbersome as the list of options gets bigger.
Illustration:
library(shiny)
shinyApp(
ui = shinyUI({
fluidPage(
title = "Example for ordering objects",
sidebarLayout(
sidebarPanel(uiOutput("selection"),
actionButton('update',"Update")),
mainPanel(
helpText('The order of elements'),
tableOutput('theorder')
)
)
)
}),
server = function(input, output, session) {
values <- reactiveValues(x = c('Item1','Item2','Item3'))
output$selection <- renderUI({
selectizeInput('neworder',
'Select new order',
choices = values$x,
multiple = TRUE)
})
output$theorder <- renderTable(
values$x
)
observeEvent(input$update,{
id <- values$x %in% input$neworder
values$x <- c(input$neworder, values$x[!id])
})
}
)
Down side. To change the order at the end of the list, the user must select the entire list in the correct order. One mistake and he must start over.
Wishlist: shiny widget (possibly from another package), drag and drop desirable, which might make this action more convenient.
source to share
You can use a custom input object for this . There are a bunch of libraries for this, here is an example from this one .
Here is a flamboyant binding code in javascript to be included in www
your application folder :
sortableList.js
var sortableListBinding = new Shiny.InputBinding();
$.extend(sortableListBinding, {
find: function(scope) {
return $(scope).find(".sortableList");
},
getValue: function(el) {
if (typeof Sortable.active != 'undefined'){
return Sortable.active.toArray();
}
else return "";
},
subscribe: function(el, callback) {
$(el).on("change.sortableListBinding", function(e) {
callback();
});
},
unsubscribe: function(el) {
$(el).off(".sortableListBinding");
},
initialize: function(el) {
Sortable.create(el,{
onUpdate: function (evt) {
$(el).trigger("change");
}});
}
});
Shiny.inputBindings.register(sortableListBinding);
It's very minimal, it creates a Sortable instance on initialization and returns the order of the items whenever the user changes them using the onUpdate
library event .
Here's an example app with R code to create an item:
app.R
library(shiny)
library(htmlwidgets)
sortableList <- function(inputId, value) {
tagList(
singleton(tags$head(tags$script(src="http://rubaxa.github.io/Sortable/Sortable.js"))),
singleton(tags$head(tags$script(src = "sortableList.js"))),
tags$div(id = inputId,class = "sortableList list-group",
tagList(sapply(value,function(x){
tags$div(class="list-group-item","data-id"=x,x)
},simplify = F)))
)
}
ui <- fluidPage(
sortableList('sortable_list',c(2,3,4)),
sortableList('sortable_list2',c(5,6,7)),
textOutput('test'),
textOutput('test2')
)
server <- function(input, output, session)
{
output$test <- renderText({input$sortable_list})
output$test2 <- renderText({input$sortable_list2})
}
shinyApp(ui=ui, server=server)
The function sortableList
includes both the library Sortable
and sortableList.js
Shiny glue code and creates divs holding the values of the vector passed to value
.
My app directory looks like this:
<application-dir>
|-- www
|-- sortableList.js
|-- app.R
And I am using runApp("application-dir")
to run the application.
source to share
You can also use the Shiny htmlwidget
: listviewer widget which supports editable and sortable .
source to share