1

Here is a simple app where the user can either type (numericInput, bpm_numeric) or slide (sliderInput, bpm_slider) the input value. Both of these UI elements should stay in-sync with either choice. However, the output only needs to be tracked once. Below I am tracking it twice, but I only need one copy of the "selection/input" from the user. I think I need to use reactiveValues, but I am not sure of the approach.

library(shiny)

ui <- fluidPage(

    titlePanel("Reverb & Delay Calculator"),

    sidebarLayout(
        sidebarPanel(
            uiOutput("bpm_numeric"),
            uiOutput("bpm_slider")
        ),
        mainPanel(
            p("Numeric value is:"),
            verbatimTextOutput("numeric_val"),
            p("Slider value is:"),
            verbatimTextOutput("slider_val")
        )
    )
)

server <- function(input, output, session) {

    output$bpm_numeric = renderUI({
        numericInput(
            "bpm_numeric",
            "BPM Numeric",
            value = 60, min = 40, max = 300
        )
    })

    output$bpm_slider = renderUI({
        sliderInput(
            "bpm_slider",
            "BPM Slider",
            value = 60, min = 40, max = 300
        )
    })

    # See how I can track "both", but really I only need to track "one" since
    # they should stay in-sync with one another. Maybe reactiveValues???
    output$numeric_val <- renderText({ input$bpm_numeric })
    output$slider_val <- renderText({ input$bpm_slider })

    observeEvent(input$bpm_numeric, {
        val = input$bpm_numeric
        updateSliderInput(session, "bpm_slider", value = val)
    })

    observeEvent(input$bpm_slider, {
        val = input$bpm_slider
        updateNumericInput(session, "bpm_numeric", value = val)
    })

}

shinyApp(ui = ui, server = server)

Shiny App

NOTE: While I am currently using uiOuput() + renderUI() + update___Input(), this is not necessarily a requirement. I am open to other approaches provided that the input UI's stay in sync I have one copy of the synced output.

6
  • 3
    If you only need to respond to one of them why do you have two renderText blocks? The observeEvent blocks make sure that they stay in sync, from there anything that needs that value should be able to depend on only one of them. Commented Mar 25, 2019 at 16:04
  • why don't you just delete one of the renderText outputs? Commented Mar 25, 2019 at 16:37
  • @r2evans - I suppose I'm looking to generalize rather than just "pick" one of the two? I agree the choice is arbitrary, but I would like to think there's some method of abstracting this vs. locking in to one particular choice? Commented Mar 25, 2019 at 17:08
  • Perhaps I'm missing some of what you're saying, but to me the generic solution is precisely "depend on only one of them". In SQL, you have coalesce that gives you just the first non-null of its arguments; it seems like you want something akin to that here. I don't find any utility in that, frankly, since: (1) I know the id of all of them so can pick one, and (2) I control the up-to-date-ness of all candidates. In a more complicated example, trying to conditionally depend on more than one can easily result in repeat-dependencies, causing at best flickering, possibly unnecessary computation. Commented Mar 25, 2019 at 17:29
  • Are you trying to generalize "how to use that number", or how to make sure that "2 or more UI elements are kept in sync to the same value"? Commented Mar 25, 2019 at 17:31

1 Answer 1

1

Here's a thought. It is able to generalize a little, but it still relies on you knowing perfectly which ids you want to combine in this manner.

Mods to your current example:

  1. Define a global defbmp <- 60, useful to make sure all elements are starting at the same place.
  2. Outside of the new useval reactive, replace all references to input$bpm_numeric or _slider with useval().
  3. Useful for state-tracking, add lastval reactive value.
  4. Finally, the useval <- eventReactive({},{}) block, depending on each of the to-be-synced elements.

Final product (excluding the ui component, no changes there):

defbpm <- 60                                       # new

server <- function(input, output, session) {

    output$bpm_numeric = renderUI({
        numericInput(
            "bpm_numeric",
            "BPM Numeric",
            value = defbpm, min = 40, max = 300    # update
        )
    })

    output$bpm_slider = renderUI({
        sliderInput(
            "bpm_slider",
            "BPM Slider",
            value = defbpm, min = 40, max = 300    # update
        )
    })

    output$numeric_val <- renderText({ useval() }) # update
    output$slider_val <- renderText({ useval() })  # update

    lastval <- shiny::reactiveVal(defbpm)          # new
    useval <- eventReactive({
      input$bpm_numeric
      input$bpm_slider
    }, {
      newval <- setdiff(c(input$bpm_numeric, input$bpm_slider), lastval())
      if (length(newval) > 0) {
        if (newval != input$bpm_numeric) updateNumericInput(session, "bpm_numeric", value = newval)
        if (newval != input$bpm_slider) updateSliderInput(session, "bpm_slider", value = newval)
        lastval(newval)
      }
      lastval()
    })                                             # new

}

Ways to improve this (I don't know at the moment):

  1. programmatically define the elements to keep synced, such as a character vector of ids;
  2. update eventReactive to have the conditional expression be generated programmatically, I'm just not a guru-enough to do this at the moment; and
  3. determine at run-time if a given id represents a numericInput or a sliderInput (or something else), so that the update* functions can be called more generically.
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.