0

I am trying to dynamically generate user inputs based on multiple predefined lists, my goal is to ease code maintainability by allowing it to conform to predefined data structures. Below is the code and the explanation for a simplified version of the problem I am having:

library(shiny)

distributions <- list(
  "Distribution 1" = list("Distribution 1 Parameter 1" = list("Lower" = 0, "Upper" = Inf, "Default" = 1))
)

models <- list(
  "General Model 1" = list("Specific Model 1")
)

parameters <- list(
  "Specific Model 1" = list("Specific Model 1 Parameter 1")
)

ui <- fluidPage(
  # Model selection 
  fluidRow(
    selectInput("generalModel", "General Model", names(models)),
    selectInput("specificModel", "Specific Model", models[[1]]),
    tabsetPanel(
      # Tab for inputs
      tabPanel(
        title = "Inputs",
        uiOutput("inputsTabs")
      )
    )
  )
)  

server <- function(input, output, session) {
  # Handling model selection
  observe({
    updateSelectInput(session, "specificModel", choices = models[[input$generalModel]])
  })

  # Displaying inputs for a specific model in tabs
  output$inputsTabs <- renderUI({
    nTabs = length(parameters[[input$specificModel]])
    tabs = lapply(seq_len(nTabs), function(i) {
      tabPanel(
        paste0(parameters[[input$specificModel]][[i]]),
        uiOutput(paste0(input$specificModel, "Input", parameters[[input$specificModel]][[i]]))
      )
    })
    do.call(tabsetPanel, tabs)
  })

  # Creating inputs for each specific parameter
  observe(
    lapply(seq_len(length(parameters[[input$specificModel]])), function(i) {
      output[[paste0(input$specificModel, "Input", parameters[[input$specificModel]][[i]])]] <- renderUI({
        distribution <- paste0(input$specificModel, "Input", parameters[[input$specificModel]][[i]], "Distribution")
        selectInput(inputId = distribution, label = "Distribution",  choices = names(distributions))
        uiOutput(paste0(distribution, "Parameters"))
      })
    }), 
    priority = 100
  )

  # Creating inputs for each specific parameter
  observe(
    lapply(seq_len(length(parameters[[input$specificModel]])), function(i) {
      distribution <- paste0(input$specificModel, "Input", parameters[[input$specificModel]][[i]], "Distribution")
      output[[paste0(distribution, "Parameters")]] <- renderUI({
        nNumericInputs = length(distributions[[input[[distribution]]]])
        numericInputs = lapply(seq_len(nNumericInputs), function(i) {
          numericInput(
            inputId = paste0(distribution, input[[input[[distribution]]]], distributions[[input[[distribution]]]][[i]]),
            label = paste0(distributions[[input[[distribution]]]][[i]]),
            value = distributions[[input[[distribution]]]][[i]][[3]],
            max = distributions[[input[[distribution]]]][[i]][[2]],
            min = distributions[[input[[distribution]]]][[i]][[1]]
          )
        })
        do.call(wellPanel, numericInputs)
      })
    }),
    priority = 50
  )

}

shinyApp(ui = ui, server = server)

Image of the app

All the user inputs described below are generated based on the parameters, models, and distributions lists defined before the UI in the shiny app above.

So the user starts by selecting a general model followed by a specific model from the two drop down menus respectively. Then based on whatever the specific model is, the Inputs tab is filled with tabs corresponding to the parameters of the chosen model. The user can then choose the distribution for each parameter from a drop down menu inside the tab.

After that the user should be able to populate a series of numeric inputs corresponding to the distribution parameters. I tried to dynamically generate those numeric inputs but I keep getting:

Error: attempt to select less than one element in get1index

The code of my attempt is at the end of the app and is commented out, uncomment it to reproduce the error.

0ld M4j0r
  • 23
  • 4
  • `input[[distribution]]` returns null on my machine. `distribution` has the value `Specific Model 1InputSpecific Model 1 Parameter 1Distribution` while the elements of `input` are named `generalModel` and `specificModel` – Gregor de Cillia Jul 04 '17 at 12:55
  • @GregordeCillia do you know why the `selectInput` with the `inputId` that is `distribution` isn't an element of input? and is there a way to make it an element of input? – 0ld M4j0r Jul 04 '17 at 13:07
  • The input gets created on the clientside when the ui element is displayed. This is not the case, when you call `input[[distribution]]` since `renderUI` hasn't returned yet – Gregor de Cillia Jul 04 '17 at 13:20
  • So to solve the issue I am having, I need to re-implement my code in a way such that `selectInput` is in the `ui` part of the app rather that the `server` – 0ld M4j0r Jul 04 '17 at 13:29
  • This is one possibility. Another one is to use the `priority` argument of `observe` to control the order in which several ui components are generated – Gregor de Cillia Jul 04 '17 at 13:38
  • I edited the code above to use the `priority` argument of `observe`, but I am still getting the same error. Is there any thing fundamentally wrong in the way I am trying to implement this interface? – 0ld M4j0r Jul 04 '17 at 17:19

0 Answers0