Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

R Shiny modules with conditionalPanel and reactives

Tags:

module

r

shiny

I am trying to modularize a complex Shiny app for which I have a conditionalPanel that should only appear given a certain input state.

Before I made everything modular, the input and conditionalPanel were both in ui.R, and I could reference the input using something like this:

conditionalPanel("input.select == 'Option one'", p('Option one is selected'))

Now that I have modularized things, accessing the input is more complicated. I thought the following was the way to do it, but it doesn't quite work. (Here I've combined things into a single standalone script):

library(shiny)

## Module code for 'selectorUI' and 'selector'
selectorUI <- function(id) {
  ns <- NS(id)
  selectizeInput(inputId = ns('select'),
                 label = 'Make a choice:',
                 choices = c('Option one', 'Option two'))
}

selector <- function(input, output, session) {
  reactive(input$select)
}

## Main app
ui <- shinyUI(fluidPage(
  selectorUI('id1'),
  conditionalPanel(condition = "output.selected == 'Option one'", p('Option one is selected.'))
))

server <- shinyServer(function(input, output, session) {
  output$selected <- callModule(selector, 'id1')
})

shinyApp(ui = ui, server = server)

I think this should work, but it doesn't - it only works if I make another reference to output$selected in the main ui section:

ui <- shinyUI(fluidPage(
  selectorUI('id1'),
  textOutput('selected'),   ## Adding just this one line makes the next line work
  conditionalPanel(condition = "output.selected == 'Option one'", p('Option one is selected.'))
))

Unfortunately of course this has the unwanted effect of rendering the result of textOutput('selected'). I can only guess that the reason this works is because it somehow triggers the reactive in a way that the JavaScript reference alone does not.

Any idea how I should be getting this conditionalPanel to work properly?

Thank you..

EDIT: Turns out not actually a bug: https://github.com/rstudio/shiny/issues/1318. See my own answer below.

But also note that I actually like the renderUI solution given in the accepted answer better than my original conditionalPanel approach.

like image 552
Brian Stamper Avatar asked Aug 23 '16 19:08

Brian Stamper


2 Answers

After calling the module the ID of selectizeInput is id1-select. In javaScript there are two ways of accessing object properties:

objectName.property or objectName['property']

Since there is - in the ID we have to refer to it via string, so the second method is way to go.

The condition in conditionalPanel becomes:

input['id1-select'] == 'Option one'

Full example:

library(shiny)

## Module code for 'selectorUI' and 'selector'
selectorUI <- function(id) {
  ns <- NS(id)
  selectizeInput(inputId = ns('select'),
                 label = 'Make a choice:',
                 choices = c('Option one', 'Option two'))
}

## Main app
ui <- shinyUI(fluidPage(
  selectorUI('id1'),
  conditionalPanel(condition = "input['id1-select'] == 'Option one'",
                   p('Option one is selected.'))
))

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

})

shinyApp(ui = ui, server = server)

EDIT:

This does work, but doesn't it violate the notion of modularity? You would have to know the code for the module internally calls that input 'select' in order to construct 'id1-select'.

Yes, you're right.

According to this article, the trick you used i.e. assigning a module call to the output$selected and then accessing its value on the client side via output.selected should work but it doesn't. I don't know why...it is maybe a bug. (I have the newest shiny version from github)

The only thing I can think of is to use renderUI instead of conditionalPanel as in the example below:

library(shiny)
## Module code for 'selectorUI' and 'selector'
selectorUI <- function(id) {
  ns <- NS(id)
  selectizeInput(inputId = ns('select'),
                 label = 'Make a choice:',
                 choices = c('Option one', 'Option two'))
}

selector <- function(input, output, session) {
  reactive(input$select)
}

## Main app
ui <- shinyUI(fluidPage(
  selectorUI('id1'),
  uiOutput("dynamic1")
))

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


  output$dynamic1 <- renderUI({
    condition1 <- callModule(selector, 'id1') # or just callModule(selector, 'id1')()
    if (condition1() == 'Option one') return(p('Option one is selected.'))
  })

})

shinyApp(ui = ui, server = server)
like image 88
Michal Majka Avatar answered Oct 30 '22 22:10

Michal Majka


Turns out it actually isn't a bug, just a little tricky. According to Joe Cheng,

Right--we don't, by default, calculate/render output values if they aren't going to be visible. And if we don't calculate them, you can't use them in conditions.

You can change this behavior this by setting an output to calculate every time, you can use this in your server.R (replace outputId with the corresponding value):

outputOptions(output, "outputId", suspendWhenHidden = FALSE)

So to fix the problem with my original example, we only need to add that one line to the server function:

server <- shinyServer(function(input, output, session) {
  output$selected <- callModule(selector, 'id1')
  outputOptions(output, 'selected', suspendWhenHidden = FALSE) # Adding this line
})
like image 31
Brian Stamper Avatar answered Oct 30 '22 22:10

Brian Stamper