I'm trying to add and remove uiOutput
elements using an index to keep track of each individual element. There is an actionButton
for adding and element to the list, and an x button for each element that removes the selected item, as in the image below:
I'm using a single .Rmd file that includes both ui
and server
code. My current solution (with which I cannot produce the desired functionality shown above---it basically does nothing) is the following:
actionButton("addFilter", "Add filter", icon=icon("plus", class=NULL, lib="font-awesome"))
i <- 0
observeEvent(input$addFilter, {
i <<- i + 1
uiOutput(paste("filterPage",i,sep=""))
output[[paste("filterPage",i,sep="")]] = renderUI({
fluidPage(
fluidRow(
column(6, selectInput(paste("filteringFactor",i,sep=""), "Choose factor to filter by:",
choices=c("factor A", "factor B", "factor C"), selected="factor B",
width="100%")),
column(6, actionButton(paste("removeFactor",i,sep=""), "",
icon=icon("times", class = NULL, lib = "font-awesome")))
)
)
})
observeEvent(input[[paste("removeFactor",i,sep="")]], {
output[[paste("filterPage",i,sep="")]] = renderUI({})
})
})
When I put uiOutput
and the remove-button observeEvent
outside of the add-button observeEvent
the code works, but I need to have a separate statement per index, as follows:
uiOutput(paste("filterPage",1,sep=""))
uiOutput(paste("filterPage",2,sep=""))
uiOutput(paste("filterPage",3,sep=""))
uiOutput(paste("filterPage",4,sep=""))
actionButton("addFilter", "Add filter", icon=icon("plus", class=NULL, lib="font-awesome"))
i <- 0
observeEvent(input$addFilter, {
i <<- i + 1
output[[paste("filterPage",i,sep="")]] = renderUI({
fluidPage(
fluidRow(
column(6, selectInput(paste("filteringFactor",i,sep=""), "Choose factor to filter by:",
choices=c("factor A", "factor B", "factor C"), selected="factor B",
width="100%")),
column(6, actionButton(paste("removeFactor",i,sep=""), "",
icon=icon("times", class = NULL, lib = "font-awesome")))
)
)
})
})
observeEvent(input[[paste("removeFactor",1,sep="")]], {
output[[paste("filterPage",1,sep="")]] = renderUI({})
})
observeEvent(input[[paste("removeFactor",2,sep="")]], {
output[[paste("filterPage",2,sep="")]] = renderUI({})
})
observeEvent(input[[paste("removeFactor",3,sep="")]], {
output[[paste("filterPage",3,sep="")]] = renderUI({})
})
observeEvent(input[[paste("removeFactor",4,sep="")]], {
output[[paste("filterPage",4,sep="")]] = renderUI({})
})
I couldn't make a for loop or lapply
call to work (it looks like a scoping problem I do not understand). As the number of elements is not known in advance hardcoding the values will not work for me. Does anybody know how to make this work? Thanks.
I have a fix for you, that might not be beautiful on the backend, but makes life very easy. I'd suggest you nest all your elements within each other. Make the uiOutput
number i
contain the uiOutput
for the next one. This way, you can add them successively without needing to worry about size or total number of elements. Performance-wise this will also be okay, especially since I don't think you are creating thousands of filters. See code for more details.
For deleting, you have already seen that it's tedious to keep track of all button input values. So I'd suggest you design one variable to observe, which tells you the element number, that has been clicked. We can achieve this with custom onclick
function for our buttons, sending the button number to one single input variable. You might want to make yourself familiar with the JavaScript client function Shiny.onInputChange
. Just briefly: it sends a value to the R backend under the given variable name.
Deleting the corresponding ui element is easy.
If you want to get more into this and design a more clear, more fine solution, try this page and related questions. There you can get input on how to design and add chunks of dynamic ui into a document without the commonly used single uiOutput
wrapper.
Code below (I made a regular ui-server app):
library(shiny)
ui <- shinyUI(
fluidPage(
actionButton("addFilter", "Add filter", icon=icon("plus", class=NULL, lib="font-awesome")),
uiOutput("filterPage1")
)
)
server <- function(input, output){
i <- 0
observeEvent(input$addFilter, {
i <<- i + 1
output[[paste("filterPage",i,sep="")]] = renderUI({
list(
fluidPage(
fluidRow(
column(6, selectInput(paste("filteringFactor",i,sep=""), "Choose factor to filter by:",
choices=c("factor A", "factor B", "factor C"), selected="factor B",
width="100%")),
column(6, actionButton(paste("removeFactor",i,sep=""), "",
icon=icon("times", class = NULL, lib = "font-awesome"),
onclick = paste0("Shiny.onInputChange('remove', ", i, ")")))
)
),
uiOutput(paste("filterPage",i + 1,sep=""))
)
})
})
observeEvent(input$remove, {
i <- input$remove
output[[paste("filterPage",i,sep="")]] <- renderUI({uiOutput(paste("filterPage",i + 1,sep=""))})
})
}
shinyApp(ui, server)
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