-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
observeEvent stay registred after destroy() and removed the observed item #1486
Comments
Hmmm, I might not be understanding your issue very well. But it seems to me like you are making it more complicated than it needs to be (nesting an library(shiny)
ui <- fluidPage(
actionButton('insertBtn', 'Create My Button'),
tags$div(id = 'placeholder')
)
server <- function(input, output, session) {
storage <- reactiveValues(isCreated = FALSE)
observeEvent(input$insertBtn, {
if (!storage$isCreated) {
print(paste0(format(Sys.time(), "%H:%M:%S"), " Insert"))
insertUI(
selector = "#placeholder",
ui = tags$div(id = "my-button",
actionButton("btnRemove", "Remove this button"))
)
storage$isCreated <- TRUE
} else {
message("The button has already been created!")
}
})
observeEvent(input$btnRemove, {
print(paste0(format(Sys.time(), "%H:%M:%S"), " Remove"))
removeUI(selector = "#my-button")
storage$isCreated <- FALSE
})
}
shinyApp(ui = ui, server = server) Let me know! |
I create simple example, where I missed note, that the name of new button have to be set by user. I want create new section of configuration for example. (Each section have name defined by user and can be removed.) UPDATE: library(shiny)
ui <- fluidPage(
textInput("btnName", "Button name", value = ""),
actionButton('insertBtn', 'Create button'),
tags$div(id = 'placeholder')
)
server <- function(input, output, session) {
storage <- reactiveValues(observers = list())
myPrint <- function(...) {
print(paste0(c(format(Sys.time(), "%H:%M:%S "), c(...)),collapse = ""))
}
observeEvent(input$insertBtn, {
btnName <- input$btnName
if(btnName == "") {
myPrint("Button name can not be empty.")
return()
}
if(btnName %in% names(storage$observers)) {
myPrint("Button '", btnName, "' already exists.")
return()
}
myPrint("Insert button: ", btnName)
insertUI(
selector = "#placeholder",
ui = tags$div(id = "my-button", actionButton(btnName, paste0("Remove button: ", btnName)))
)
observer <- observeEvent(input[[btnName]], {
myPrint("Remove ", btnName)
removeUI(selector = "#my-button")
storage$observers[[btnName]]$destroy()
storage$observers <- storage$observers[-which(names(storage$observers) == btnName)]
})
observer <- list(observer)
names(observer) <- btnName
storage$observers <- c(storage$observers, observer)
})
}
shinyApp(ui = ui, server = server)
|
Hmm, this took me some time, but I still think the best solution (and more conceptually correct) is to stick to an observer for each button. You have to be a bit sneaky with the observer for the remove button, but it's nothing from another world. I modified the app quite a bit while trying to figure out the best approach. Here it is, again let me know if I missed something: library(shiny)
ui <- fluidPage(
textInput("divID", "Enter div ID:", ""),
actionButton("isrt", "Create My Button"),
tags$div(id = "placeholder")
)
server <- function(input, output, session) {
storage <- reactiveValues(divID = NULL, btnID = NULL)
# take a dependency on `isrt` button
observeEvent(input$isrt, {
# handle the case when user does not provide ID
divID <- if (input$divID == "") "id" else input$divID
# only create button if there is none
if (is.null(storage$divID)) {
insertUI(
selector = "#placeholder",
ui = tags$div(id = divID,
actionButton(paste0(divID, "rmv"), "Remove this button"))
)
# store the ids of the containing div and the 'remove' button
storage$divID <- divID
storage$btnID <- paste0(divID, "rmv")
# otherwise, print a message to the console
} else {
message("The button has already been created!")
}
})
observeEvent({
# take a dependency on `rmv` button *if* it exists
id <- isolate(storage$btnID)
if (!is.null(id)) input[[id]]
# necessary for the first time around (otherwise
# this observer would *never* get run)
else storage$btnID
},
{
# fail silently if the `rmv` button does not exist (again,
# this is only necessary for the first time around)
req(storage$btnID)
# if the `rmv` button *does* exist, then make sure it has
# been clicked
req(input[[storage$btnID]])
removeUI(selector = paste0("#", storage$divID))
# reset state back to original setting
storage$divID <- NULL
storage$btnID <- NULL
})
}
shinyApp(ui = ui, server = server) |
[Aside to @jcheng5: do you think this pattern is important enough for us to make some user-friendly wrappers? It's really not trivial to get it to work in this edge case, but it is possible. I've been looking at this for a while, so I'm having trouble judging whether this is a very particular situation or if it something more and more people are likely to run into...] |
Your app works just for one button. I'm not able create the second one. Variable storage$btnID is common for all, isn't it? |
|
I need add more buttons with dynamic names and then can remove some of them. |
Alright, I think I finally understood what you wanted and the best way to achieve it. We needed to add a new argument to devtools::install_github("rstudio/shiny", ref="barbara/observe")
library(shiny) Then, run this app: ui <- fluidPage(
textInput("divID", "Enter an ID for the custom area:", ""),
helpText("Leave the text input blank for automatically unique IDs."),
actionButton("isrt", "Add a button"),
tags$div(id = "placeholder")
)
server <- function(input, output, session) {
rv <- reactiveValues()
# take a dependency on `isrt` button
observeEvent(input$isrt, {
# handle the case when user does not provide ID
divID <- if (input$divID == "") gsub("\\.", "", format(Sys.time(), "%H%M%OS3"))
else input$divID
btnID <- paste0(divID, "rmv")
# only create button if there is none
if (is.null(rv[[divID]])) {
insertUI(
selector = "#placeholder",
ui = tags$div(id = divID,
paste0("Welcome, ", divID, "!"),
actionButton(btnID, "Remove this area",
class = "pull-right btn btn-danger"),
style = "background-color: #e0cda7;
height: 50px;
margin: 10px;
padding: 5px;
display: block;
border-radius: 5px;
border: 2px solid #2a334f;"
)
)
# make a note of the ID of this section, so that it is not repeated accidentally
rv[[divID]] <- TRUE
print("created")
# create a listener on the newly-created button that will
# remove it from the app when clicked
obs <- observeEvent(input[[btnID]], {
removeUI(selector = paste0("#", divID))
rv[[divID]] <- NULL
print("destroyed")
obs$destroy()
}, skipFirst = TRUE)
# otherwise, print a message to the console
} else {
message("The button has already been created!")
}
})
}
shinyApp(ui = ui, server = server) As you can see, the second I also must say that I was wrong: for your particular use case, it's a great idea to nest the observers. I did not realize at first that you wanted to have multiple dynamically created buttons at the same time in the app. Let me know if you have any feedback. Thanks for the great catch! |
I'm out of my PC, I'll look on it after New year. Thanks and happy christmas |
I tried install your branch, but it doesn't exists. devtools::install_github("rstudio/shiny", ref="barbara/observe") |
The branch has already been merged into master, so just enter: devtools::install_github("rstudio/shiny")
library(shiny) And then the app: ui <- fluidPage(
textInput("divID", "Enter an ID for the custom area:", ""),
helpText("Leave the text input blank for automatically unique IDs."),
actionButton("isrt", "Add a button"),
tags$div(id = "placeholder")
)
server <- function(input, output, session) {
rv <- reactiveValues()
# take a dependency on `isrt` button
observeEvent(input$isrt, {
# handle the case when user does not provide ID
divID <- if (input$divID == "") gsub("\\.", "", format(Sys.time(), "%H%M%OS3"))
else input$divID
btnID <- paste0(divID, "rmv")
# only create button if there is none
if (is.null(rv[[divID]])) {
insertUI(
selector = "#placeholder",
ui = tags$div(id = divID,
paste0("Welcome, ", divID, "!"),
actionButton(btnID, "Remove this area",
class = "pull-right btn btn-danger"),
style = "background-color: #e0cda7;
height: 50px;
margin: 10px;
padding: 5px;
display: block;
border-radius: 5px;
border: 2px solid #2a334f;"
)
)
# make a note of the ID of this section, so that it is not repeated accidentally
rv[[divID]] <- TRUE
print("created")
# create a listener on the newly-created button that will
# remove it from the app when clicked
observeEvent(input[[btnID]], {
removeUI(selector = paste0("#", divID))
rv[[divID]] <- NULL
print("destroyed")
}, ignoreInit = TRUE, once = TRUE)
# otherwise, print a message to the console
} else {
message("The button has already been created!")
}
})
}
shinyApp(ui = ui, server = server) |
Great! It's what I need. The main different is that you used new arguments |
Yep, that's right. See the documentation in |
I looked in to documentation, but I missed it there. https://shiny.rstudio.com/reference/shiny/latest/observeEvent.html |
Yeah, the documentation on the website is for the latest released version, not for the latest development version, which is why it is not up-to-date. But we're rolling out a new Shiny release soon, so you can count on this being out on CRAN within a few weeks. |
Hi,
I want dynamically add and remove new buttons and I encountered a problem when I want create button with same name which was already used (it also has been removed).
How replicate trouble:
In the third step was new button created and also removed. When you click on the insert button again, the new button will be create.
My workaround was used add random suffix of button name.
The text was updated successfully, but these errors were encountered: