Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Shiny Input Update Reactives

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");
like image 371
SharpSharpLes Avatar asked Nov 07 '22 05:11

SharpSharpLes


1 Answers

If you replace the following line in receiveMessage:

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

with

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

it works.

like image 92
SeGa Avatar answered Nov 11 '22 03:11

SeGa