I am using a Shiny application in which it may take some time to set a slider to the right value.
So while trying to set the slider to the right value (and not releasing
Shiny's sliderInput
uses Ion.RangeSlider, which has an onFinish
callback for when a user releases their mouse. That sounds like just what we need.
Here's a custom input binding for a "lazy" slider input that only signals a value change when a user releases their mouse (onFinish
) or when the slider is forcibly updated (onUpdate
).
For the example, I just inlined code that changes the behavior of ALL sliderInputs. You'll want to move this to an external script and customize further. Also, onUpdate
gets called when the slider is initialized, but before Shiny initializes input values. You have to wait until Shiny does this to call the value change callback in onUpdate
. The solution I worked in isn't fantastic, but I was too lazy to find a cleaner way.
library(shiny)
ui <- fluidPage(
tags$head(
tags$script(HTML("
(function() {
var sliderInputBinding = Shiny.inputBindings.bindingNames['shiny.sliderInput'].binding;
var lazySliderInputBinding = $.extend({}, sliderInputBinding, {
subscribe: function(el, callback) {
var $el = $(el);
var slider = $el.data('ionRangeSlider');
var handleChange = function() {
if (!inputsInitialized) return;
callback(!$el.data('immediate') && !$el.data('animating'));
};
slider.update({
onUpdate: handleChange,
onFinish: handleChange
});
},
unsubscribe: function(el, callback) {
var slider = $(el).data('ionRangeSlider');
slider.update({
onUpdate: null,
onFinish: null
});
}
});
Shiny.inputBindings.register(lazySliderInputBinding, 'shiny.lazySliderInput');
var inputsInitialized = false;
$(document).one('shiny:connected', function() {
inputsInitialized = true;
});
})();
"))
),
sliderInput("sliderA", "A", 0, 10, 5),
uiOutput("sliderB"),
verbatimTextOutput("sliderValues"),
actionButton("resetSliders", "Reset Sliders")
)
server <- function(input, output, session) {
observeEvent(input$resetSliders, {
updateSliderInput(session, "sliderA", value = 5)
updateSliderInput(session, "sliderB", value = c(4, 6))
})
output$sliderB <- renderUI({
sliderInput("sliderB", "B", 0, 10, c(4, 6))
})
output$sliderValues <- renderPrint({
cat(paste("Slider A =", input$sliderA), "\n")
cat(paste("Slider B =", paste(input$sliderB, collapse = " ")))
})
}
shinyApp(ui, server)
Alright, after some digging, I think I've found the solution. It appears the cleanest way to do this, without needing to mess with Java script, would be to use the shinyCustom
package, which has a useShinyCustom
and customSliderInput
function. With these, you can set the reactivity to "debounce" and also mess with the delay time. If you were to use these options together, you should be able to find the sweet spot for only updating the output once the slider stops moving. It won't be a perfect "on release of mouse button;" but it should be pretty darn close!
For example, you may try something like:
# Slider delay type is actually "debounce" by default
useShinyCustom(slider_delay = "500"), # Doubles the default delay time in ms
customSliderInput("bins", #Use customSliderInput instead of sliderInput
"Number of bins:",
min = 1,
max = 50,
value = 30)