4

(I read through this question and, despite the similar titles, it is not relevant to my problem—or, if it is, I'm too dumb to see how it applies.)

I'm working on modularizing my Shiny code so that adding additional graphs only requires the addition of a couple of functions in a separate file. There are three panels—a side-panel (wherein a user selects a graph), a bottom-panel (wherein a user selects graph-parameters), and a main panel (wherein the graph is displayed).

The side-panel doesn't change, but the bottom panel changes according to what's selected in the side-panel.

side_panel.R

# UI function
sidePanelInput <- function(id, label='side panel') { # Some input w/ ns = selected_graph }

# Server function
sidePanel <- function(input, output, session) {
    selected_graph <- reactive({input$selected_graph})
    return(selected_graph)

In my app.R file, selected_graph gets passed to both the bottom panel and the main panel:

app.R

# ...
sidePanel <- callModule(sidePanel, 'side')
bottomPanel <- callModule(bottomPanel, 'bottom', data=some_data, selected_graph=sidePanel)
mainPanel <- callModule(mainPanel, "main", data=some_data, selected_graph=sidePanel, params=bottomPanel)

# ...

So far so good (notice that the bottomPanel also return something, and that gets passed to mainPanel). All of this passing back and forth works well. Here's my problem: The bottom panel for each graph is different, and defined in a separate file. This means that the bottomPanel module needs to know what to render from the reactive that sidePanel spits out. This also means that I don't use a UI function for bottomPanel, I only use a server function w/ renderUI:

bottom_panel.R

source('graphs')
bottomPanel <- function(input, output, session, data, selected_graph) {
    # Call the function of the graph, depending on what the selected graph is
    output$bottomPanel <- renderUI({
        tagList(
            match.fun(paste(selected_graph(), '_bottom_panel', sep=''))(session$ns('id'))
        )
    })

    # So, if the selected graph is 'scatter_1', then the function call will be
    # scatter_1_bottom_panel(session$ns('id')) -- An example of a bottom_panel function
    # is provided at the end of this question, but it works as intended

    # Now, we set the defaults (specific to the graph); for example, slider-ranges
    # will be set according to mins and maxes in the data. Similar to above, a 
    # match.fun() call is used here to determine how the defaults are set
    observe({
        match.fun(paste(selected_graph(), '_bottom_panel_defaults', sep=''))(session, data)
    })

    # Here is my problem. I need to output the parameters of the newly-rendered
    # bottom panel, so that those parameters can be passed to the main panel. This
    # as it is doesn't work, because one apparently can't read from server output
    params <- reactive({output$bottomPanel})
    return(params)
}

How can I output the parameters of the rendered UI after it's rendered and the default-value function is called?


example_bottom_panel.R

scat_2_bottom_panel <- function(id) {
    ns <- NS(id)
    panel <- wellPanel(
        sliderInput(
            inputId = ns('duration_range'),
            label = 'Duration of Sound [ms]',
            min = 0,
            max = 10000,
            value = c(0, 10000),
            step = 100,
            round = FALSE,
            ticks = TRUE
        )
    )
    return(panel)
}

example_default_function.R

scatter_1_bottom_panel_defaults <- function(session, data) { 
    updateSliderInput(session, 'duration_range', value=c(min(data$duration), max(data$duration)))
}

I've read through the above-linked question a few times more, and it seems like this is what was done in the server function:

xvar <- reactive({input[[ "xvar" ]] })
yvar <- reactive({input[[ "yvar" ]] })

And then xvar and yvar were used as parameters in the renderUI call. At first glance, this doesn't work for me; the reactive values necessary for each bottom panel change according to the graph that the user selected. Maybe I can include the renderUI call within the bottom_panel function, declare these IDs as reactive, and use them in the panel generation?

AmagicalFishy
  • 1,249
  • 1
  • 12
  • 36
  • 1
    Can you make a small reproducing example? If I understand correct, you want to have the side panel return some reactive value, which is dynamically used by the bottom panel? – Kota Mori Aug 24 '18 at 15:27
  • @KotaMori Not quite. I want to have the bottom panel return the parameters generated in the server function's `renderUI` call (so that these can be passed to the main panel). The rendered UI is called via a separate function, and dependent on the selection of the side panel. I'll work on a small reproducing example, but I fear that it won't be much smaller than what I've posted above! – AmagicalFishy Aug 24 '18 at 15:34
  • @KotaMori Just to reiterate, my problem isn't getting reactive values from the side panel and passing it to the bottom panel—it's sending values from the bottom panel (values that are generated from a variable `renderUI` call, not from a UI function) – AmagicalFishy Aug 24 '18 at 15:36
  • 1
    I see... Maybe the issue is that "outputs" are not reactive objects, so we cannot pass around like we can do for "inputs". What exactly do you want to pass? I don't think passing `output$bottomPanel` makes sense. Perhaps you want to pass the "the input from the dynamically created UI"? – Kota Mori Aug 24 '18 at 15:42
  • @KotaMori Yes! That's exactly what I want to pass. I'm just not sure how to do that from within the server function (since that is where the UI is rendered). Right now, I am attempting to declare IDs in the UI-creation function as reactive, and then return those IDs in the function itself. – AmagicalFishy Aug 24 '18 at 15:46

1 Answers1

7

To retrieve input values from dynamically created object (through renderUI),

  • Use session$ns to access the namespace in the server module
  • Name the dynamically created object as ns("ID").

Here is a simple example where

  1. You pick the unit in the first ui/module, pass it to the second and third.
  2. You pick the value in the second ui/module, pass to the third.
  3. Show the selected value in the third ui/module.

Is this in line with what you want to do?

library(shiny)


setUnitUI <- function(id) {
  ns <- NS(id)
  selectInput(ns('unit'), 'unit', c('km', 'mile'))
}

setValueUI <- function(id) {
  ns <- NS(id)
  uiOutput(ns('dynamicSlider'))
}

showValueUI <- function(id) {
  ns <- NS(id)
  textOutput(ns('value'))
}

ui <- fluidPage(
  setUnitUI('unit'),
  setValueUI('value'),
  showValueUI('show')
)


setUnitModule <- function(input, output, session) {
  reactive(input$unit)
}

setValueModule <- function(input, output, session, unitGetter) {
  output$dynamicSlider <- renderUI({
    ns <- session$ns
    unit <- unitGetter()
    if (unit == 'km') {
      sliderInput(ns('pickValue'), paste('Pick value in', unit), 
                  min=0, max=150, value=0)
    } else {
      sliderInput(ns('pickValue'), paste('Pick value in', unit), 
                  min=0, max=100, value=0)
    }
  })

  reactive(input$pickValue)
}

showValueModule <- function(input, output, session, unitGetter, valueGetter) {
  output$value <- renderText(paste('You chose', valueGetter(), unitGetter()))
}

server <- function(input, output, session) {
  unitGetter <- callModule(setUnitModule, 'unit')
  valueGetter <- callModule(setValueModule, 'value', unitGetter)
  callModule(showValueModule, 'show', unitGetter, valueGetter)
}


shinyApp(ui, server, options=list(launch.browser=TRUE))
Kota Mori
  • 6,510
  • 1
  • 21
  • 25
  • This example is brilliant. After hours of trying, this has finally helped me understand the issue and I could solve. Thanks! – larnsce Jul 31 '19 at 09:06
  • @kota is it imperative to call the server modules & UI modules inside the main app ui/server in order to pass the values across different modules ? I have `Navar module` called from the main app Server and UI. The `Navbar module` calls different `tab modules`. For ex. `tab1 module` has separate calls for `inputs module` , &different `plot modules` . The `InputUI` does not render UI in the app when I call it in `tab1UI` module, using renderUI from `InputServer`. But, if I list the inputs in the `InputUI` directly I see the inputs ( selectInputs, checkboxes, buttons etc) renders as expected. – user5249203 Sep 20 '19 at 01:59
  • If you have answered anything similar, please direct me to your answer else I can post a Q – user5249203 Sep 20 '19 at 01:59
  • @user5249203 I don't think I have answered such a question before. I would be happy to think about your case if you post as a question. – Kota Mori Sep 21 '19 at 02:34