Search code examples
rshiny

Automated generation of input GUI (for function parameters)?


Has anyone come across a solution that would take a list of named items and generate a corresponding set of shiny input fields? With checkboxes where the input class is logical, etc.

The particular envisioned use case is to -in the context of a run of the mill "render yourR analysis accessible to non-specialists" project- be able to extract a underlying function's parameters and defaults using formals() and feed the resulting list (filtered against symbols etc.) into the magic functionality outlined above, generating a gui representation of the function interface without the need to manually choose every single element.


Solution

  • Here’s a starting point, but in general there’s just not enough information in function formals to be able to reliably construct inputs.

    generate_input_ui <- function(x, id) {
      UseMethod("generate_input_ui")
    }
    
    generate_input_ui.function <- function(x, id) {
      params <- formals(x)
      env <- environment(x)
      args <- lapply(params, function(param) {
        tryCatch(eval(param, env), error = function(e) NULL)
      })
      shiny::tagList(
        Map(generate_input_ui, args, paste0(id, "-", names(args)))
      )
    }
    

    Add some methods for common types.

    generate_input_ui.default <- function(x, id) {
      shiny::textInput(id, label = id, value = "")
    }
    
    generate_input_ui.logical <- function(x, id) {
      shiny::checkboxInput(id, label = id, value = isTRUE(x))
    }
    
    generate_input_ui.numeric <- function(x, id) {
      shiny::numericInput(id, label = id, value = c(x, 0)[1])
    }
    
    generate_input_ui.character <- function(x, id) {
      shiny::textInput(id, label = id, value = c(x, "")[1])
    }
    

    Try it out.

    foo <- function(w, x = TRUE, y = 42, z = "Hello") NULL
    generate_input_ui(foo, "foo") |> capture.output() |> cat(sep = "\n")
    #> <div class="form-group shiny-input-container">
    #>   <label class="control-label" id="foo-w-label" for="foo-w">foo-w</label>
    #>   <input id="foo-w" type="text" class="shiny-input-text form-control" value=""/>
    #> </div>
    #> <div class="form-group shiny-input-container">
    #>   <div class="checkbox">
    #>     <label>
    #>       <input id="foo-x" type="checkbox" class="shiny-input-checkbox" checked="checked"/>
    #>       <span>foo-x</span>
    #>     </label>
    #>   </div>
    #> </div>
    #> <div class="form-group shiny-input-container">
    #>   <label class="control-label" id="foo-y-label" for="foo-y">foo-y</label>
    #>   <input id="foo-y" type="number" class="shiny-input-number form-control" value="42"/>
    #> </div>
    #> <div class="form-group shiny-input-container">
    #>   <label class="control-label" id="foo-z-label" for="foo-z">foo-z</label>
    #>   <input id="foo-z" type="text" class="shiny-input-text form-control" value="Hello"/>
    #> </div>