I have a Shiny application where I would like to add a UI element using an action button and then have that inserted ui be dynamic.
Here is my current ui file:
library(shiny)
shinyUI(fluidPage(
div(id="placeholder"),
actionButton("addLine", "Add Line")
))
and server file:
library(shiny)
shinyServer(function(input, output) {
observeEvent(input$addLine, {
num <- input$addLine
id <- paste0("ind", num)
insertUI(
selector="#placeholder",
where="beforeBegin",
ui={
fluidRow(column(3, selectInput(paste0("selected", id), label=NULL, choices=c("choice1", "choice2"))))
})
})
})
If choice1
is selected within the specific ui element, I would like to add a textInput
to the row. If choice2
is selected within the ui element, I would like to add a numericInput
.
While I generally understand how to create reactive values that change in response to user input, I don't know what to do here because I do not know how to observe an element that has not been created yet and that I do not know the name of. Any help would be very appreciated!
That's because it's reactive: the app must load, trigger a reactive event, which calls the server function, yielding HTML to insert into the page. This is one of the downsides of renderUI() ; relying on it too much can create a laggy UI.
A Shiny app consists of two parts, a user interface ( ui ) and an R session that runs code and returns results ( server ). These two parts can be in their own files called ui. R and server. R, or they can be combined into a single file called app.
Shiny is based on a reactive programming model, similar to a spreadsheet. Spreadsheet cells can contain literal values, or formulas that are evaluated based on other cells. Whenever the value of the other cells change, the value of the formula is automatically updated. Shiny apps behave the same way.
An expression that returns a Shiny tag object, HTML() , or a list of such objects. env. The environment in which to evaluate expr . quoted.
Code
This can be easily solved with modules:
library(shiny)
row_ui <- function(id) {
ns <- NS(id)
fluidRow(
column(3,
selectInput(ns("type_chooser"),
label = "Choose Type:",
choices = c("text", "numeric"))
),
column(9,
uiOutput(ns("ui_placeholder"))
)
)
}
row_server <- function(input, output, session) {
return_value <- reactive({input$inner_element})
ns <- session$ns
output$ui_placeholder <- renderUI({
type <- req(input$type_chooser)
if(type == "text") {
textInput(ns("inner_element"), "Text:")
} else if (type == "numeric") {
numericInput(ns("inner_element"), "Value:", 0)
}
})
## if we later want to do some more sophisticated logic
## we can add reactives to this list
list(return_value = return_value)
}
ui <- fluidPage(
div(id="placeholder"),
actionButton("addLine", "Add Line"),
verbatimTextOutput("out")
)
server <- function(input, output, session) {
handler <- reactiveVal(list())
observeEvent(input$addLine, {
new_id <- paste("row", input$addLine, sep = "_")
insertUI(
selector = "#placeholder",
where = "beforeBegin",
ui = row_ui(new_id)
)
handler_list <- isolate(handler())
new_handler <- callModule(row_server, new_id)
handler_list <- c(handler_list, new_handler)
names(handler_list)[length(handler_list)] <- new_id
handler(handler_list)
})
output$out <- renderPrint({
lapply(handler(), function(handle) {
handle()
})
})
}
shinyApp(ui, server)
Explanation
A module is, well, a modular piece of code, which you can reuse as often as you want without bothering about unique names, because the module takes care of that with the help of namespaces
.
A module consists of 2 parts:
UI
functionserver
functionThey are pretty much like the normal UI
and server
functions, with some things to keep in mind:
namespacing
: within the server you can access elements from the UI
as you would do normally, i.e. for instance input$type_chooser
. However, at the UI
part, you have to namespace
your elements, by using NS
, which returns a function which you can conveniently use in the rest of the code. For this the UI
function takes an argument id
which can be seen as the (unique) namespace for any instance of this module. The element ids
must be unique within the module and thanks to the namespace, they will be also unique in the whole app, even if you use several instances of your module.UI
: as your UI
is a function, which only has one return value, you must wrap your elements in a tagList
if you want to return more than one element (not needed here).server
: you need the session
argument, which is otherwise optional. If you want your module to communicate with the main application, you can pass in a (reactive) argument which you can use as usual in your module. Similarly, if you want your main application to use some values from the module you should return reactives as shown in the code. If you ened to creat UI
elements from your server function you also need to namespace them and you cann acces the namespacing function via session$ns
as shown.usage
: to use your module you insert the UI
part in your main app by calling the function with an unique id
. Then you have to call callModule
to make the server logic work, where you pass in the same id
. The return value of this call is the returnValue
of your module server function and can be sued to work with values from within the module also in the main app. This explains modules in a nutshell. A very good tutorial which explains modules in much more detail and completeness can be found here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With