In my Shiny application, I have the functionality to insert a new line of information, this is done via InsertUI. Then for every added line, the source of that line can be updated via an actionButton. The actionButton leads to a modalDialog, allowing the user to insert the text for the source there.
To prevent multiple lines getting the same source after updating, a moduleServer is used for the part around the modalDialog. That works well, with the only exception that after the first time the modalDialog is used (with inside an action button that updates the text after insertion), the modalDialog closes directly after clicking. Reclicking does give the pop-up again, but is somewhat annoying.
Any suggestions to prevent this?
Example UI and server with this behaviour:
UI.R
library(shiny)
shinyUI(fluidPage(
div(id = "input_add_div_block"),
br(),
# Input for the new line
div(style="display:inline-block", # div inline-block is used to get two input boxes on the same line
textInput(inputId = "input_new_line",
label = "Give description of new line")
),
# Adding a new line
div(style="display:inline-block", # div inline-block is used to get two input boxes on the same line
id = "input_add_div",
actionButton("input_add", "Add new line")
)
))
Server.R
library(shiny)
shinyServer(function(input, output, session) {
data <- reactiveValues(
lines = list()
)
observeEvent(input$input_add, {
current_lines <- length(data$lines)
n <- current_lines + 1
insertUI(selector = "#input_add_div_block",
where = "beforeEnd", # insert after last element
ui = fluidPage(
div(style="display:inline-block"), # div inline-block is used to get two input boxes on the same line
# Adding input field
numericInput(inputId = paste0("input_", n),
label = paste0(input$input_new_line, " - % of previous line"),
value = 10,
min = 0,
max = 100
),
actionButton(inputId = paste0("input_", n,"_source"),
label = "No source")
)
)
observeEvent(input[[paste0("input_", current_lines+1,"_source")]],{
update_source_UI(id = paste0("source",n), data = data, n = n)
update_source_Server(id = paste0("source",n), data = data, n = n, original_session = session)
})
})
})
update_source_UI <- function(id, data, label = "Update UI", n){
ns <- NS(id)
showModal(
modalDialog(
title = "Update source",
textInput(inputId = ns("new_source"), label = "Update the source",
value = "Assumption"),
actionButton(inputId = ns("add_source"),
label = "Update the source"),
"\n",
actionButton(inputId = ns("cancel"),
label = "Cancel"),
easyClose = TRUE,
footer = NULL
)
)
}
# Pop-up to change the source of the funnel line input
update_source_Server <- function(id, data, n, original_session){
moduleServer(
id,
function(input, output, session) {
ns <- session$ns
# When the user clicks "Update the source", the source is updated
observeEvent(input$add_source,{
updateActionButton(session = original_session,
inputId = paste0("input_", n,"_source"),
label = paste0("Source: ", input$new_source))
removeModal()
data$button_status[[1]] <- FALSE
})
# When the user clicks "Cancel", the modal is removed without changes
observeEvent(input$cancel,{
removeModal()
})
}
)
}
A few points:
I've never seen showModule in the UI part, normally you use it in the server; it's interesting that it works nevertheless
at least in your example, n is always 1, therefore you don't have unique ids which leads to problems
I find passing session objects to modules to refer to something defined in the main server quite complicated
I propose to pack everything for one line into a module and then handle the logic there. I think it's easier and you already work with modules.
My take:
library(shiny)
one_line_UI <- function(id, input_new_line){
ns <- NS(id)
tagList(
div(style="display:inline-block"), # div inline-block is used to get two input boxes on the same line
# Adding input field
numericInput(inputId = ns("input_number"),
label = paste0(input_new_line(), " - % of previous line"),
value = 10,
min = 0,
max = 100
),
actionButton(inputId = ns("input_source"),
label = "No source")
)
}
# Pop-up to change the source of the funnel line input
one_line_Server <- function(id, data){
moduleServer(
id,
function(input, output, session) {
ns <- session$ns
observeEvent(input$input_source, {
showModal(
modalDialog(
title = "Update source",
textInput(inputId = ns("new_source"), label = "Update the source",
value = "Assumption"),
actionButton(inputId = ns("add_source"),
label = "Update the source"),
"\n",
actionButton(inputId = ns("cancel"),
label = "Cancel"),
easyClose = TRUE,
footer = NULL
)
)
})
# When the user clicks "Update the source", the source is updated
observeEvent(input$add_source,{
updateActionButton(inputId = "input_source",
label = paste0("Source: ", input$new_source))
removeModal()
data$button_status[[1]] <- FALSE
})
# When the user clicks "Cancel", the modal is removed without changes
observeEvent(input$cancel,{
removeModal()
})
}
)
}
ui <- fluidPage(
div(id = "input_add_div_block"),
br(),
# Input for the new line
div(style="display:inline-block", # div inline-block is used to get two input boxes on the same line
textInput(inputId = "input_new_line",
label = "Give description of new line")
),
# Adding a new line
div(style="display:inline-block", # div inline-block is used to get two input boxes on the same line
id = "input_add_div",
actionButton("input_add", "Add new line")
)
)
server <- function(input, output, session) {
data <- reactiveValues(
lines = list(),
n = 0
)
observeEvent(input$input_add, {
n <- data$n + 1
insertUI(selector = "#input_add_div_block",
where = "beforeEnd", # insert after last element
ui = one_line_UI(id = n,
input_new_line = reactive({input$input_new_line}))
)
one_line_Server(id = n, data = data)
data$n <- n
})
}
shinyApp(ui, server)
If you want more information, you can also check out my tutorial how to dynamically add modules.
Related
I am new to shiny and am currently trying to develop my first shinyapp.
This apps contains multiple actionButtons and nested observeEvents statements, which I think are the cause of my problem.
The app should allow the user to add observations of species by clicking on a add button, that updates the UI. Within each observation, more details can be asked, but I only showed the species name in the REPREX below (textinput).
Each observation can be deleted individually via a delete button.
Until here, it works! However, I also want a modal dialog to confirm the deletion when the delete button is clicked. To do this, I used a nested observeEvent and it doesn't seem to work (or maybe only for the first time). What am I doing wrong ?
Thanks in advance to anyone who tries to help me.
library(shiny)
library(random)
ui <- fluidPage(
fluidRow(br(), br(), actionButton("adder",
label = "Add an observation"),
align="center")
)
server <- function(input, output,session) {
rv <- reactiveValues()
rv$GridId_list <- c()
observeEvent(input$adder,{
# create random ID for each added species
GridId <- as.character(randomStrings(1, 10))
# store the new ID
rv$GridId_list <- c(rv$GridId_list,GridId)
# ID for the textinput
SpId <- paste(GridId, "sp", sep="_")
# ID of the button used to remove this species
removeSpeciesId <- paste(GridId,'remover', sep="_")
#Update of the UI
insertUI(
selector = '#adder',
where = "beforeBegin",
ui = tags$div(
id = GridId,
fluidRow(
column(6,
h5("Species name : "),
textInput(SpId,label = NULL)
),
column(6, align = "center",
br(),br(),
actionButton(removeSpeciesId,
label = "Delete")
)
)
)
)
# Remove an observation when the "delete" button is clicked (and after confirmation)
observeEvent(input[[removeSpeciesId]], {
#Confirmation modal
showModal(
modalDialog(
"Are you sure ?",
title = "Delete",
footer = tagList(
actionButton("cancel", "Cancel"),
actionButton("confirm", "Confirm", class = "btn btn-danger")
)
)
)
# Delete observation if user confirms
observeEvent(input$confirm, {
id_to_remove <- substring(removeSpeciesId,1, nchar(removeSpeciesId)-8)
rv$GridId_list <- rv$GridId_list[rv$GridId_list!=id_to_remove]
removeUI(selector = paste("#", id_to_remove, sep = ""))
showNotification("Observation deleted !")
removeModal()
})
# Just remove the modal if user cancels
observeEvent(input$cancel, {
removeModal()
})
})
})
}
shinyApp(ui = ui, server = server, options = list(launch.browser = T))
Referencing dynamic input id's is a pain. I find it best to add a last clicked input identifier to reference. You can add a class to those inputs to just listen to them and not others in your app:
tags$head(tags$script(HTML("$(document).on('click', '.needed', function () {
Shiny.onInputChange('last_btn',this.id);
});")))
That little piece of code will allow you to get an input$last_btn id, that you can use for your event listeners. In this case you don't need to nest your event listeners; it is better to think about the events in sequence and program those reactions. So, with some tweakings in your code, your app now looks like this:
library(shiny)
library(random)
ui <- fluidPage(
tags$head(tags$script(HTML("$(document).on('click', '.needed', function () {
Shiny.onInputChange('last_btn',this.id);
});"))),
fluidRow(br(), br(), actionButton("adder",
label = "Add an observation"),
align="center")
)
server <- function(input, output,session) {
rv <- reactiveValues()
rv$GridId_list <- c()
observeEvent(input$adder,{
# create random ID for each added species
GridId <- as.character(randomStrings(1, 10))
# store the new ID
rv$GridId_list <- c(rv$GridId_list,GridId)
# ID for the textinput
SpId <- paste(GridId, "sp", sep="_")
# ID of the button used to remove this species
removeSpeciesId <- paste(GridId,'remover', sep="_")
#Update of the UI
insertUI(
selector = '#adder',
where = "beforeBegin",
ui = tags$div(
id = GridId,
fluidRow(
column(6,
h5("Species name : "),
textInput(SpId,label = NULL)
),
column(6, align = "center",
br(),br(),
actionButton(removeSpeciesId,
label = "Delete", class="needed")
)
)
)
)
})
# Remove an observation when the "delete" button is clicked (and after confirmation)
observeEvent(input$last_btn, {
observeEvent(input[[input$last_btn]] > 0,{#We want the modal to show when any "remover" id is clicked
#Confirmation modal
showModal(
modalDialog(
"Are you sure ?",
title = "Delete",
footer = tagList(
actionButton("cancel", "Cancel"),
actionButton("confirm", "Confirm", class = "btn btn-danger")
)
)
)
})
}, ignoreNULL = TRUE, ignoreInit = TRUE)
# Delete observation if user confirms
observeEvent(input$confirm, {
#The following selector is for the parent id of the parent id of the last_btn id
removeUI(selector = paste0("div:has(>div:has(>#", input$last_btn, "))"))
showNotification("Observation deleted !")
removeModal()
})
# Just remove the modal if user cancels
observeEvent(input$cancel, {
removeModal()
})
}
shinyApp(ui = ui, server = server, options = list(launch.browser = T))
I have an app which works with tabpanels that use many of the same inputs, and must be rendered using renderUI to respond to user data. I've noticed my modulated inputs give priority to the first menu they're rendered in and disregard changes made in different panels
The following is a simplified working example of the basic issue
library(shiny)
addexButtons <- function(id, label = "ROCParam") {
ns <- NS(id)
uiOutput(ns("roccondicionals"), label = label)
}
numbmod <- function(input, output, session, ndata) {
output$roccondicionals <- renderUI({
tagList(numericInput('numb', 'Choose Num', value = 0,))
})
}
ui <- fluidPage(navbarPage(
'App',
tabPanel(title = 'Menu 1',
sidebarLayout(
sidebarPanel(addexButtons("counter1", "Adder")),
mainPanel(textOutput('sumtotal'))
)),
tabPanel(title = 'Menu 2',
sidebarLayout(
sidebarPanel(addexButtons("counter2", "Multiplier"),),
mainPanel(textOutput('multiplytotal'))
))
))
server <- function(input, output) {
callModule(numbmod, "counter1")
callModule(numbmod, "counter2")
output$sumtotal <-
renderText(paste0('5 + ', input$numb, ' = ', input$numb + 5))
output$multiplytotal <-
renderText(paste0('5 x ', input$numb, ' = ', input$numb * 5))
}
shinyApp(ui = ui, server = server)
If you run this example you will see that, by changing to menu 2 the value retains the information modified in Menu 1 (which is intended) however if I choose to modify this same value in the same tab I can't and must return to Menu 1 to do so.
Is there a way to be able to modify the same rendered input on two different tabs where the last modification is the one retained?
As #YBS has already mentioned you cannot define two inputs with the same id. I would use updateNumericInput to automatically update the inputs when of the inputs is changed (triggered).
library(shiny)
addexButtons <- function(id, label = "ROCParam") {
ns <- NS(id)
uiOutput(ns("roccondicionals"), label = label)
}
numbmod <- function(input, output, session, ndata, n) {
output$roccondicionals <- renderUI({
numericInput(paste0("numb",n), 'Choose Num', value = 0)
})
}
ui <- fluidPage(navbarPage(
'App', id = "App",
tabPanel(title = 'Menu1',
tab_id = "tab1",
sidebarLayout(
sidebarPanel(addexButtons("counter1", "Adder")),
mainPanel(textOutput('sumtotal'))
)),
tabPanel(title = 'Menu2',
tab_id = "tab2",
sidebarLayout(
sidebarPanel(addexButtons("counter2", "Multiplier"),),
mainPanel(textOutput('multiplytotal'))
))
))
server <- function(input, output, session) {
observeEvent(input$numb1, {
updateNumericInput(session, "numb2", value = input$numb1)
updateNavbarPage(session,"App", "Menu2")
})
observeEvent(input$numb2, {
updateNumericInput(session, "numb1", value = input$numb2)
updateNavbarPage(session,"App", "Menu1")
})
callModule(numbmod, "counter1",n = 1)
callModule(numbmod, "counter2",n = 2)
output$sumtotal <-
renderText(paste0('5 + ', input$numb1, ' = ', input$numb1 + 5))
output$multiplytotal <-
renderText(paste0('5 x ', input$numb2, ' = ', input$numb2 * 5))
}
shinyApp(ui = ui, server = server)
I have an app which creates boxes. Each box has a button that triggers a modal. The modal has inputs which the user changes and then a button which triggers an action based on those inputs (basically just uploading to a database). Because each box has a different specification, I wrote a module and then loop thru a list, creating a box for each element. This works fine.
However, the flow in the modal and observeEvent has a flaw: the first run thru I get the desired results, but on the second occasion in the same box (same id module), after pressing the modal button to update, it will not use the new inputs, but rather what happened in the first run. I am guessing it has something to do with the namespace/observeEvent combination as I might be triggering the event with a "stored" namespace? Would I need to somehow "flush" the namespace after every update? Anyway, any help appreciated as it gets confusing fast with all the namespace/modules combinations.
library(shiny)
library(shinyWidgets)
ui <- navbarPage(
'page', collapsible = TRUE,
tabPanel("test",
useSweetAlert(),
sidebarLayout(
sidebarPanel(),
mainPanel(
uiOutput('all_products_ui')
)
)
)) # end navbar
server <- shinyServer(function(input, output) {
list_products <- c(1,2,3,4,5)
# Now, I will create a UI for all the products
output$all_products_ui <- renderUI({
r <- tagList()
progress_move <- 0
for(k in 1:length( list_products )){
r[[k]] <- ExistingProductUI(id = k, product = list_products[[k]] )
}
r
})
# handlers duplicate a call to module depending on the id of ExistingProductUI
handlers <- list()
observe(
handlers <<- lapply(seq.int(length( list_products )),
function(i) {
callModule(ExistingProductUpdate,
id = i,
product = list_products[[i]] )
})
)
handlers
}) # end of server ----
# UI module ------------------------------------------------------
ExistingProductUI <- function(id, product){
ns <- NS(id)
box(title = as.character(p$title),
product["title"],
footer = tagList(
actionBttn(
inputId = ns("change_selected"), label = "change"),
)
)
}
# server module ------------------------------------------------------
ExistingProductUpdate <- function(input, output, session, product){
ns <- session$ns
observeEvent(input$change_selected, {
# when box button is clicked for this product (id)
# FIRST: show a modal
showModal(
modalDialog(
title = "what do you want to change?",
tagList(
radioGroupButtons(inputId = ns("change_selected_choice"), labels = "change x", choices = c(1,2,3,4)),
sliderInput(ns("change_selected_pct"), "change y:", min = -50, max = 100, value = 0, step = 5)
),
easyClose = TRUE,
footer = tagList(
actionButton(ns("change_selected_submit"), "submit!", icon = icon("check")),
modalButton("never mind")
)
)
)
# SECOND: when change_selected_submit is clicked,
observeEvent(input$change_selected_submit, {
# do some calculations with product using what I inputed in modal ---
# then, update a table ----
functionToUploadThings(product, input$change_selected_choice)
# THIRD: Close with a confirmation
sendSweetAlert(
session,
title = "Success!",
type = "success",
btn_labels = "Ok",
closeOnClickOutside = TRUE,
width = NULL
)
})
})
}
Below is a solution that works. The problem was that you nested your observeEvent in the module. I'm not entirely sure why this led to problems, some values weren't processed correctly. However, you don't need to nest the observeEvent, the second one gets also triggered by the actionButton in the modal when it is by its own. Additionally, I included a removeModal before the success notification is shown:
library(shiny)
library(shinyWidgets)
library(shinydashboard)
ui <- navbarPage(
'page', collapsible = TRUE,
tabPanel("test",
useSweetAlert(),
sidebarLayout(
sidebarPanel(),
mainPanel(
uiOutput('all_products_ui')
)
)
)) # end navbar
server <- shinyServer(function(input, output) {
list_products <- c(1,2,3,4,5)
# Now, I will create a UI for all the products
output$all_products_ui <- renderUI({
r <- tagList()
progress_move <- 0
for(k in 1:length( list_products )){
r[[k]] <- ExistingProductUI(id = k, product = list_products[[k]] )
}
r
})
# handlers duplicate a call to module depending on the id of ExistingProductUI
handlers <- list()
observe(
handlers <<- lapply(seq.int(length( list_products )),
function(i) {
callModule(ExistingProductUpdate,
id = i,
product = list_products[[i]] )
})
)
handlers
}) # end of server ----
# UI module ------------------------------------------------------
ExistingProductUI <- function(id, product){
ns <- NS(id)
box(title = as.character(product),
product,
footer = tagList(
actionBttn(
inputId = ns("change_selected"), label = "change"),
)
)
}
# server module ------------------------------------------------------
ExistingProductUpdate <- function(input, output, session, product){
ns <- session$ns
observeEvent(input$change_selected, {
# when box button is clicked for this product (id)
# FIRST: show a modal
showModal(
modalDialog(
title = "what do you want to change?",
tagList(
radioGroupButtons(inputId = ns("change_selected_choice"), label = "change x", choices = c(1,2,3,4)),
sliderInput(ns("change_selected_pct"), "change y:", min = -50, max = 100, value = 0, step = 5)
),
easyClose = TRUE,
footer = tagList(
actionButton(ns("change_selected_submit"), "submit!", icon = icon("check")),
modalButton("never mind")
)
)
)
})
# SECOND: when change_selected_submit is clicked,
observeEvent(input$change_selected_submit, {
# do some calculations with product using what I inputed in modal ---
# then, update a table ----
# functionToUploadThings(product, input$change_selected_choice)
# THIRD: Close with a confirmation
removeModal()
sendSweetAlert(
session,
title = "Success!",
type = "success",
btn_labels = "Ok",
closeOnClickOutside = TRUE,
width = NULL
)
})
}
shinyApp(ui, server)
Please note: I made some modifications to make your MWE work:
include library(shinydashboard)
p$title and product["title"] to product
change labels to label in radioGroupButtons
comment out functionToUploadThings(product, input$change_selected_choice)
Edit
I'm still not super sure what happens when nesting the observeEvents. I made a small toy example and played around with the reactlog. It seems that nesting the observers generates a new observer for button2 every time button1 is clicked. These observers are not removed and lead to unwanted behaviour. In contrast, when using separate observeEvents, the observer for button2 is only created once.
library(shiny)
library(reactlog)
ui <- fluidPage(
actionButton("button1", "click")
)
server <- function(input, output, session) {
observeEvent(input$button1, {
print("from first observer")
print(input$button2)
showModal(
modalDialog(
title = "what do you want to change?",
"some text",
easyClose = TRUE,
footer = tagList(
actionButton("button2", "submit!", icon = icon("check")),
modalButton("never mind")
)
)
)
# nested observer -> leads to remaining observers
observeEvent(input$button2, {
print("from second observer")
print(input$button2)
removeModal()
})
})
# independent observer -> generates only one observer
# observeEvent(input$button2, {
# print("from second observer")
# print(input$button2)
# removeModal()
# })
}
shinyApp(ui, server)
I need a reactive variable (declared server-side) available after start-up. Using what I learned here How to create a conditional renderUI in Shiny dashboard I tried wrapping in reactive() before defining the UI but no luck. Moving topValuesSelector to the UI inside a conditionalPanel would work except conditional panels apparently do not like the %in% operator (a separate issue that I also tried to resolve w/o success).
if (interactive()) {
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
ui <-
dashboardPage(header = dashboardHeaderPlus(left_menu = tagList(
dropdownBlock(
id = "prefDropdown",
title = "Preferences",
icon = NULL,
badgeStatus = NULL,
checkboxGroupInput(
inputId = "prefDropdown",
label = NULL,
choices = c("Pareto",
"Legend on chart",
"Cases/1K uniques",
"Top 10 only"),
selected = c("Pareto", "Cases/1K uniques", "Top 10 only")
),
uiOutput("topValues")
)
)),
dashboardSidebar(),
dashboardBody(fluidRow(box(
title = "Top",
textOutput("topN")
))))
server <- function(input, output) {
topValuesSelector <- reactive({
if ("Top 10 only" %in% input$prefDropdown) {
numericInput(
inputId = "topValues",
label = NULL,
width = "25%",
value = 10,
min = 1,
max = 30,
step = 1
)
}
})
output$topValues <- renderUI({
topValuesSelector()
})
observe({
if ("Top 10 only" %in% input$prefDropdown) {
output$topN <- renderText(input$topValues)
} else{
output$topN <- renderText(100)
}
})
}
shinyApp(ui, server)
}
The intent is for the initial value of "topValues" to be 10 with this value immediately available. However, no value is available which causes an error. Using req() avoids the error by pausing execution but that is not a viable approach because "topValues" is needed for a plot. So no plot until selecting "prefDropdown".
It looks like the problem is that input$topValues does not exist until you click on the Preferences button. Since the UI element isn't needed it hasn't been created yet.
In order to work around that you can create a variable that detects whether or not the input is available and if not use a default value.
if (interactive()) {
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
ui <-
dashboardPage(header = dashboardHeaderPlus(left_menu = tagList(
dropdownBlock(
id = "prefDropdown",
title = "Preferences",
icon = NULL,
badgeStatus = NULL,
checkboxGroupInput(
inputId = "prefDropdown",
label = NULL,
choices = c("Pareto",
"Legend on chart",
"Cases/1K uniques",
"Top 10 only"),
selected = c("Pareto", "Cases/1K uniques", "Top 10 only")
),
uiOutput("topValues")
)
)),
dashboardSidebar(),
dashboardBody(fluidRow(box(
title = "Top",
textOutput("topN")
))))
server <- function(input, output) {
## We want to use the same default value in two places so create a var
default_value <- 10
topValuesSelector <- reactive({
if ("Top 10 only" %in% input$prefDropdown) {
numericInput(
inputId = "topValues",
label = NULL,
width = "25%",
value = default_value, ## Change to use the default value
min = 1,
max = 30,
step = 1
)
}
})
output$topValues <- renderUI({
topValuesSelector()
})
## Create a variable that is the default value unless the input is available
myTopN <- reactive({
if(length(input$topValues)>0){
return(input$topValues)
}
return(default_value)
})
observe({
if ("Top 10 only" %in% input$prefDropdown) {
# output$topN <- renderText(input$topValues)
output$topN <- renderText(myTopN()) ## Use our new variable instead of the input directly
} else{
output$topN <- renderText(100)
}
})
}
shinyApp(ui, server)
}
There are a couple of other things going on with your code. Notice that "Top 10 only" %in% input$prefDropdown will not do what you think it is doing. You have to check to see if "Top 10 only" is TRUE... I'll leave you there to start another question if you get stuck again.
I've been trying to generate a Shiny page where elements can be added or removed by the user - based on this snippet. I tried to adapt it for UI elements rather than buttons, but I've clearly done something wrong.
I've only included the add-button part here - each time the "add" button is clicked, the correct number of UI elements are added, but they seem to all be the same element rather just appending a new one to the existing list (the number in the label is the same, whereas it should be different for each one) and the initial text values are erased (or, potentially, are just the "default" from the final element repeated however many times).
How can I just add a new UI element and keep any text input that the user has entered in the already-existing elements?
Code:
library(shiny)
# function to render UI element
renderUiElement <- function(id, nameDef) {
renderUI(box(
actionButton(paste0("rmBtn", id), label = "", icon = icon("times")),
# text initial value supplied as a variable
textInput(paste0("nameText", id), label = paste("Item", id), value = nameDef)
))
}
server <- function(input, output, session) {
rv <- reactiveValues(uiComponents = list(), uiIds = list(),
nameVals = list(), lastId = 0)
# on "Add" button click:
observe({
if(is.null(input$addBtn) || input$addBtn == 0) return()
isolate({
# store current text inputs and append new default text
rv$nameVals <- lapply(rv$uiIds, function(x) input[[paste0("nameText", x)]])
rv$nameVals <- append(rv$nameVals, "New Text")
# create (and store) new id number and add it to the list of ids in use
rv$lastId <- rv$lastId + 1 # get the new code to use for these UI elements
rv$uiIds <- append(rv$uiIds, rv$lastId)
# for each id, render the UI element using the appropriate default text
for(i in seq_along(rv$uiIds)) {
thisId <- rv$uiIds[[i]]
output[[paste0("uiEl", thisId)]] <- renderUiElement(
id = thisId, nameDef = rv$nameVals[[i]]
)
}
# add the new UI element into the reactiveValues list
rv$uiComponents <- append(
rv$uiComponents,
list(
list(
uiOutput(paste0("uiEl", rv$lastId)),
br(),br()
)
)
)
})
})
# render all the UI elements inside the container
output$container <- renderUI({
rv$uiComponents
})
}
ui <- fluidPage(
titlePanel("Dynamically Add/Remove UI Elements"),
hr(),
actionButton("addBtn", "Add"),
br(),br(),
uiOutput("container")
)
shinyApp(ui, server)
Hi to dynamicly add objects shiny has the function insertUI.
Your code would look like something similar to this
library(shiny)
library(shinydashboard)
# function to render UI element
renderUiElement <- function(id, nameDef) {
box(
actionButton(paste0("rmBtn", id), label = "", icon = icon("times")),
# text initial value supplied as a variable
textInput(paste0("nameText", id), label = paste("Item", id), value = nameDef)
)
}
server <- function(input, output, session) {
# on "Add" button click:
counter <- 0
observeEvent(
input$addBtn,
{
counter <<- counter +1
insertUI(
selector = '#container',
where = "beforeEnd",
ui = renderUiElement(
id = paste0("ui",counter),
nameDef = "New Text"
),
session = session
)
},
ignoreNULL = TRUE,
ignoreInit = TRUE
)
output$container <- renderUI({
div()
})
}
ui <- fluidPage(
titlePanel("Dynamically Add/Remove UI Elements"),
hr(),
actionButton("addBtn", "Add"),
br(),br(),
uiOutput("container")
)
shinyApp(ui, server)
Hope it helps.