5

I have a custom shiny input which is a balanced set of sliders. I am trying to use the updateBalancedSliderInput so I can reset the values to their defaults. However, in my app, this updateBalancedSlider function does not kick off any reactives in order to update the outputs. Javascript and shiny example below. My assumption is I just need something in my js file to kick off the reactives on the server side. The UI updates just fine when trying to update the balanced slider, just not any outputs depending on changes in those values. Any help is much appreciated.

Shiny

### Shiny Inputs
library(shiny)

balancedSliderInput <- function(inputId, value = 0, label = "", 
                                group = "", width = "100%") {
  
  if (label != "")
    label <- paste0('<label class="control-label" for="', inputId, '">', label, '</label>')
  
  balanced_slider_tag <- tagList(
    div(style = paste("width: ", width), class = "all-balanced-slider",
        HTML(label),
        div(id = inputId, class = paste("balanced-slider", group), as.character(value)),
        span(class = "value", "0"),
        HTML("%")
    )
  )
  
  dep <- list(
    htmltools::htmlDependency("balanced_slider", "0.0.2", c(file = "www"),
                              script = c("js/jquery-ui.min.js", "js/balancedSlider.js"),
                              stylesheet = c("css/jquery-ui.min.css")
    )
  )
  
  htmltools::attachDependencies(balanced_slider_tag, dep)
}

updateBalancedSliderInput <- function(session, inputId, value = 0) {
  message <- list(value = value)
  session$sendInputMessage(inputId, message)
}

registerInputHandler("balancedSlider", function(data, ...) {
  if (is.null(data))
    NULL
  else
    data
  
}, force = TRUE)


########## App ------ 
ui <- fixedPage(
  
  actionButton("reset", "Reset", icon = icon("undo-alt")),
  balancedSliderInput("test1", label = "Test1", value = 50),
  balancedSliderInput("test2", label = "Test2", value = 50),
  textOutput("test")
  
)

server <- function(session, input, output) {
  
  test_reactive <- reactive({
    return(input$test1)
  })
  
  output$test <- renderText({
    test <- paste("Sluder 1 is at", test_reactive()[[1]])
    return(test)
  })
  
  observeEvent(input$reset, {
    updateBalancedSliderInput(session, "test1", 50)
    updateBalancedSliderInput(session, "test2", 50)
  })
  
}

shinyApp(ui, server)

Javascript

$(function() {

    $('.balanced-slider').each(function() {
        console.log("Running Log 1")
        var init_value = parseInt($(this).text());

        $(this).siblings('.value').text(init_value);

        $(this).empty().slider({
            value: init_value,
            min: 0,
            max: 100,
            range: "max",
            step: 0.5,
            animate: 0,
            slide: function(event, ui) {
              console.log("Log 10");
                
                // Update display to current value
                $(this).siblings('.value').text(ui.value);

                // Get current total
                var total = ui.value;
                var sibling_count = 0;

                var classes = $(this).attr("class").split(/\s+/);
                var selector = ' .' + classes.join('.');
                //console.log(selector);

                var others = $(selector).not(this);
                others.each(function() {
                    total += $(this).slider("option", "value");
                    sibling_count += 1;
                });

                //console.log(total);

                var delta = total - 100;
                var remainder = 0;
                
                // Update each slider
                others.each(function() {
                    console.log("Running Log 2")
                    var t = $(this);
                    var current_value = t.slider("option", "value");

                    var new_value = current_value - delta / sibling_count;
                    
                    if (new_value < 0) {
                        remainder += new_value;
                        new_value = 0;
                    }

                    t.siblings('.value').text(new_value.toFixed(1));
                    t.slider('value', new_value);

                });


                if(remainder) {
                    var pos_val_count = 0;
                    others.each(function() {
                        if($(this).slider("option", "value") > 0)
                            pos_val_count += 1;
                    });

                    others.each(function() {
                        if($(this).slider("option", "value") > 0) {
                            var t = $(this);
                            var current_value = t.slider("option", "value");

                            var new_value = current_value + remainder / pos_val_count;

                            t.siblings('.value').text(new_value.toFixed(1));
                            t.slider('value', new_value);
                        }
                    });

                }

                
            },
            // fire the callback event for the other sliders
            stop: function(event, ui) {
                var classes = $(this).attr("class").split(/\s+/);
                var selector = '.' + classes.join('.');

                $(selector).not(this).each(function() {
                   $(this).trigger("slidestop");
                });
            }
        });
    });
});

var balancedSliderBinding = new Shiny.InputBinding();

$.extend(balancedSliderBinding, {
  find: function(scope) {
    return $(scope).find(".balanced-slider");
  },

  // The input rate limiting policy
  getRatePolicy: function() {
    return {
      // Can be 'debounce' or 'throttle'
      policy: 'debounce',
      delay: 500
    };
  },

  getType: function() {
    return "balancedSlider";
  },

  getValue: function(el) {
    var obj = {};
    obj[$(el).attr("id")] = $(el).slider("option", "value");
    return obj;
  },

  setValue: function(el, new_value) {
    $(el).slider('value', new_value);
    $(el).siblings('.value').text(new_value);

  },

  subscribe: function(el, callback) {
    $(el).on("slidestop.balancedSliderBinding", function(e) {
      callback(); // add true parameter to enable rate policy
    });
  },
  
  unsubscribe: function(el) {
    $(el).off(".balancedSliderBinding");
  },

  // Receive messages from the server.
  // Messages sent by updateUrlInput() are received by this function.
  receiveMessage: function(el, data) {
    if (data.hasOwnProperty('value'))
      this.setValue(el, data.value);

    $(el).trigger('change');
  },
});

Shiny.inputBindings.register(balancedSliderBinding, "balancedSliderBinding");
SharpSharpLes
  • 302
  • 4
  • 20
  • I should also note that the updateBalancedSlider button kicked off the "receiveMessage" and "setValue" javascript functions. While clicking and letting go kicked off the "getValue" functions. Not entirely sure it's relevant, but thought I would note. I tried putting in "this.getValue(el);" into the setValue() function since it appeared that's what the difference was, but it didn't work. – SharpSharpLes Sep 09 '20 at 18:47
  • does the code you provided run? where is `jquery-ui.min.js`? – Waldi Sep 12 '20 at 20:19
  • Yes! Should be a working example. I didn't add that code here. It's a lot and didn't think it would be relevant to paste all that code here. What's the best practice for external libraries and reprexes? it comes from https://jqueryui.com – SharpSharpLes Sep 13 '20 at 22:52
  • 1
    Actually jquery already is a dependency of shiny: `library/shiny/www/shared/jqueryui/jquery-ui.min.js` – ismirsehregal Sep 14 '20 at 06:48
  • It works for me, the output `test` is changing when changing the slider. it is just not updating the output after hitting the `Reset` button? Is that what you mean? – SeGa Sep 14 '20 at 12:01
  • @SeGa – Yes, that is the problem. Sorry if that's not clearly defined enough. The sliders work by themselves. The problem is that hitting the reset button doesn't kick off the "reactive" to update the text. – SharpSharpLes Sep 14 '20 at 13:41

1 Answers1

1

If you replace the following line in receiveMessage:

$(el).trigger('change');

with

$(el).trigger('slidestop');

it works.

SeGa
  • 9,454
  • 3
  • 31
  • 70