95

In a shiny app (by RStudio), on the server side, I have a reactive that returns a list of variables by parsing the content of a textInput. The list of variables is then used in selectInput and/or updateSelectInput.

I can't make it work. Any suggestions?

I have made two attempts. The first approach is to use the reactive outVar directly into selectInput. The second approach is to use the reactive outVar in updateSelectInput. Neither works.

server.R

shinyServer(
  function(input, output, session) {

    outVar <- reactive({
        vars <- all.vars(parse(text=input$inBody))
        vars <- as.list(vars)
        return(vars)
    })

    output$inBody <- renderUI({
        textInput(inputId = "inBody", label = h4("Enter a function:"), value = "a+b+c")
    })

    output$inVar <- renderUI({  ## works but the choices are non-reactive
        selectInput(inputId = "inVar", label = h4("Select variables:"), choices =  list("a","b"))
    })

    observe({  ## doesn't work
        choices <- outVar()
        updateSelectInput(session = session, inputId = "inVar", choices = choices)
    })

})

ui.R

shinyUI(
  basicPage(
    uiOutput("inBody"),
    uiOutput("inVar")
  )
)

A short while ago, I posted the same question at shiny-discuss, but it has generated little interest, so I'm asking again, with apologies, https://groups.google.com/forum/#!topic/shiny-discuss/e0MgmMskfWo

Edit 1

@Ramnath has kindly posted a solution that appears to work, denoted Edit 2 by him. But that solution does not address the problem because the textinput is on the ui side instead of on the server side as it is in my problem. If I move the textinput of Ramnath's second edit to the server side, the problem crops up again, namely: nothing shows and RStudio crashes. I found that wrapping input$text in as.character makes the problem disappear.

Edit 2

In further discussion, Ramnath has shown me that the problem arises when the server attempts to apply the dynamic function outVar before its arguments have been returned by textinput. The solution is to first check whether is.null(input$inBody) exists.

Checking for existence of arguments is a crucial aspect of building a shiny app, so why did I not think of it? Well, I did, but I must have done something wrong! Considering the amount of time I spent on the problem, it's a bitter experience. I show after the code how to check for existence.

Below is Ramnath's code with textinput moved to the server side. It crashes RStudio so don't try it at home. (I have used his notation)

library(shiny)
runApp(list(
  ui = bootstrapPage(
    uiOutput('textbox'),  ## moving Ramnath's textinput to the server side
    uiOutput('variables')
  ),
  server = function(input, output){
    outVar <- reactive({
      vars <- all.vars(parse(text = input$text))  ## existence check needed here to prevent a crash
      vars <- as.list(vars)
      return(vars)
    })

    output$textbox = renderUI({
      textInput("text", "Enter Formula", "a=b+c")
    })

    output$variables = renderUI({
      selectInput('variables2', 'Variables', outVar())
    })
  }
))

The way I usually check for existence is like this:

if (is.null(input$text) || is.na(input$text)){
  return()
} else {
  vars <- all.vars(parse(text = input$text))
  return(vars)
}

Ramnath's code is shorter:

if (!is.null(mytext)){
  mytext = input$text
  vars <- all.vars(parse(text = mytext))
  return(vars)
}

Both seem to work, but I'll be doing it Ramnath's way from now on: maybe an unbalanced bracket in my construct had earlier prevented me to make the check work? Ramnath's check is more direct.

Lastly, I'd like to note a couple of things about my various attempts to debug.

In my debugging quest, I discovered that there is an option to "rank" the priority of "outputs" on the server side, which I explored in an attempt to solve my problem, but didn't work since the problem was elsewhere. Still, it's interesting to know and seems not very well known at this time:

outputOptions(output, "textbox", priority = 1)
outputOptions(output, "variables", priority = 2)

In that quest, I also tried try:

try(vars <- all.vars(parse(text = input$text)))

That was pretty close, but still did not fix it.

The first solution I stumbled upon was:

vars <- all.vars(parse(text = as.character(input$text)))

I suppose it would be interesting to know why it worked: is it because it slows things down enough? is it because as.character "waits" for input$text to be non-null?

Whatever the case may be, I am extremely grateful to Ramnath for his effort, patience and guidance.

micstr
  • 5,080
  • 8
  • 48
  • 76
PatrickT
  • 10,037
  • 9
  • 76
  • 111
  • 2
    `renderUI` is used for input elements that change dynamically. In your case, the `textInput` is better placed in the UI, since there are no dynamic elements involved. – Ramnath Feb 02 '14 at 03:25
  • @ Ramnath, this is a stripped-down example: I do have dynamic elements in my setup :-) – PatrickT Feb 02 '14 at 03:45
  • On the very first line of my question, I wrote on the __server__ side. If you read my code, you'll see that it covers all the usual situations. It is odd that one needs to wrap the input with `as.character` on the `server` side but not on the `ui` side. Would you consider that a bug? Or is that a feature you would have expected? Oh! someone downvoted, oh well ... – PatrickT Feb 02 '14 at 04:06
  • It is not a bug. If you first check whether `input$inBody` exists before doing the `all.vars` call, it still works. So it is not the `as.character` that really matters. Here is a [gist](https://gist.github.com/ramnathv/76c98dde2e4c87073239) with what I mean. – Ramnath Feb 02 '14 at 04:39

2 Answers2

109

You need to use renderUI on the server side for dynamic UIs. Here is a minimal example. Note that the second drop-down menu is reactive and adjusts to the dataset you choose in the first one. The code should be self-explanatory if you have dealt with shiny before.

runApp(list(
  ui = bootstrapPage(
    selectInput('dataset', 'Choose Dataset', c('mtcars', 'iris')),
    uiOutput('columns')
  ),
  server = function(input, output){
    output$columns = renderUI({
      mydata = get(input$dataset)
      selectInput('columns2', 'Columns', names(mydata))
    })
  }
))

EDIT. Another Solution using updateSelectInput

runApp(list(
  ui = bootstrapPage(
    selectInput('dataset', 'Choose Dataset', c('mtcars', 'iris')),
    selectInput('columns', 'Columns', "")
  ),
  server = function(input, output, session){
    outVar = reactive({
      mydata = get(input$dataset)
      names(mydata)
    })
    observe({
      updateSelectInput(session, "columns",
      choices = outVar()
    )})
  }
))

EDIT2: Modified Example using parse. In this app, the text formula entered is used to dynamically populate the dropdown menu below with the list of variables.

library(shiny)
runApp(list(
  ui = bootstrapPage(
    textInput("text", "Enter Formula", "a=b+c"),
    uiOutput('variables')
  ),
  server = function(input, output){
    outVar <- reactive({
      vars <- all.vars(parse(text = input$text))
      vars <- as.list(vars)
      return(vars)
    })

    output$variables = renderUI({
      selectInput('variables2', 'Variables', outVar())
    })
  }
))
Ramnath
  • 54,439
  • 16
  • 125
  • 152
  • Thanks Ramnath, my problem is a little different I think. I've been successful doing what you do here with `names()` in another app. But here I'm trying to pass `outVar()` to `selectInput()`, where `outVar()` outputs a reactive list. It might be a problem with `outVar()`, but I don't see what, or a problem of timing in the sequence of events. Maybe it's a silly mistake too. Do you see something wrong in my code? Do I need to be explicit about `names()` or about `get()`? Thanks! – PatrickT Jan 31 '14 at 06:20
  • If I write `return(list("a","b"))` right at the top of `outVar()` then it works. But if I leave the code as given above and look in the console, then `str(outVar())` returns exactly what `str(list("a","b"))` returns (visually speaking), so I think mine is a problem of timing. The `outVar()` is not ready at the time `selectInput()` needs the `choices`, could that be the problem? – PatrickT Jan 31 '14 at 06:26
  • AFAIK, if you need dynamic UI elements, you need to either use `renderUI` or an `updateSelectInput` method. This overcomes the timing problem that you are referring to. – Ramnath Jan 31 '14 at 07:32
  • Thanks Ramnath, my code above has an `updateSelectInput` method, but it's not working. Any idea? Thanks! – PatrickT Jan 31 '14 at 07:34
  • I think the problem I have is related to the sequence of steps, I tried setting `outputOptions(output, "inBody", 1)` and `outputOptions(output, "inVar", 2)` in the hope that `updateSelectInput` would have the patience to wait for `outVar()` to be ready, but it makes no difference, so perhaps the problem is elsewhere. Can you make an example with `parse` instead of a known list? Thanks for trying anyhow! – PatrickT Jan 31 '14 at 16:00
  • thanks again! I just notice your second edit, after I wrote an answer to my own question. I'm not sure why your version works, because as far as I can see the only difference between the version in your second edit and the version I posted in my question is that your `textinput` is on the __ui__ side instead of on the __server__ side ... As I explain in my answer, I found that I had to wrap the `input$text` part in `as.character` for it to work on the server side... – PatrickT Feb 02 '14 at 00:01
  • just made an edit to my question explaining that the problem crops up when `textinput` is on the server side. – PatrickT Feb 02 '14 at 00:15
  • Thank you for your answer, I would like to what to do if the `selectInput` is in a variableUI. Thank you – John Smith Jun 05 '20 at 22:09
4

server.R

### This will create the dynamic dropdown list ###

output$carControls <- renderUI({
    selectInput("cars", "Choose cars", rownames(mtcars))
})


## End dynamic drop down list ###

## Display selected results ##

txt <- reactive({ input$cars })
output$selectedText <- renderText({  paste("you selected: ", txt() ,sep="") })


## End Display selected results ##

ui.R

uiOutput("carControls"),
  br(),
  textOutput("selectedText")
tospig
  • 7,762
  • 14
  • 40
  • 79
Ashish Markanday
  • 1,300
  • 10
  • 11