9

I'd like to generate a dynamic number of actionButtons, and then have each generated button print its number to the console. This is my best attempt so far, but I still can't get the observeEvent for each of the first 10 buttons to recognize the button clicks. How do I tie the buttons to an observeEvent?

library(shiny)

ui <- basicPage(
  fluidRow(
    actionButton(inputId = "add_button",
                 label = "Add Button")
    ),
  uiOutput("more_buttons")
)

server <- function(input, output){

  rvs <- reactiveValues(buttons = list(actionButton(inputId = "button1",
                                               label = 1)))

  observeEvent(eventExpr = input$add_button,
               handlerExpr = {
                 len <- length(rvs$buttons) + 1
                 rvs$buttons[[len]] <- actionButton(inputId = paste0("button",len),
                                             label = len)
               })

  output$more_buttons <- renderUI({
    do.call(fluidRow, rvs$buttons)
  })

  # This is the part that doesn't work
  for(ii in 1:10){
    observeEvent(eventExpr = input[[paste0("button",ii)]],
                 handlerExpr = print(ii))
  }

}

shinyApp(ui, server)
Sam Helmich
  • 949
  • 1
  • 8
  • 18

2 Answers2

6

Your really close, just wrap the observeEvent part in local.

library(shiny)

ui <- basicPage(
  fluidRow(
    actionButton(inputId = "add_button",
                 label = "Add Button")
  ),
  uiOutput("more_buttons")
)

server <- function(input, output){

  rvs      <- reactiveValues(buttons = list(actionButton(inputId = "button1",
                                                    label = 1)))

  observeEvent(eventExpr = input$add_button,
               handlerExpr = {
                 len      <- length(rvs$buttons) + 1
                 rvs$buttons[[len]] <- actionButton(inputId = paste0("button",len),
                                                    label = len)
               })

  output$more_buttons <- renderUI({
    do.call(fluidRow, rvs$buttons)
  })

  observeEvent(rvs$buttons,{
    for(ii in 1:length(rvs$buttons)){
      local({
        i <- ii
        observeEvent(eventExpr = input[[paste0("button",i)]],
                     handlerExpr = {print(sprintf("You clicked btn number %d",i))})
      })
    }
  })

}

shinyApp(ui, server)
RmIu
  • 4,357
  • 1
  • 21
  • 24
  • This only works if I have up to 10 buttons. The length of the loop needs to be reactive, which is sort of the hard part. Would be curious if you have a solution. You can't dynamically generate `observeEvents` within an `observe` as far as I can tell. – Carl Oct 20 '16 at 21:44
  • Good catch, I haven't noticed that the generation of the observers worked for only up to 10 buttons. I'll update my answer. If you want to see how to do all the generation and binding of observers dynamically maybe my anwser from here can help http://stackoverflow.com/questions/39330299/probabilistic-multiple-choice-test-sliderinputs-sum-to-1-constraint/39379538#39379538 – RmIu Oct 21 '16 at 10:29
  • Also, there is another problem, you are generating multiple observeEvent for each button, so when you click on a button, it appears multiple lines telling the button you have just clicked. Everytime you update the buttons, a new observeEvent is generated. The only way I've found to circunvent this is to keep track in a reactiveValues all observeEvent that have been generated, and then check it before creating new ones. As I do here: https://stackoverflow.com/questions/45035083/nesting-two-observeevents-duplicates-the-reactive-event – Jose Lozano Jul 26 '17 at 06:52
4

Let the inputIds of the buttons to follow a pattern like "button1", "button2", "button3", use regex to isolate those inputIds from the 'input' object in the observeEvent trigger, and convert the result to a list:

 observeEvent(
     lapply(
        names(input)[grep("button[0-9]+",names(input))],
        function(name){
           input[[name]]
        }
     ),
    {
      code to run when any button with inputId matching the regex is pressed
    }
  )
Victor Burnett
  • 588
  • 6
  • 10
  • Ignoring the missing paren, this doesn't work and returns `Can't index reactivevalues with \`[\``. Your original code will run, but it triggers constantly at least in shinydashboard pages. – D3SL Apr 04 '21 at 12:30
  • Thanks. I've edited the post to fix the typo. – Victor Burnett Apr 04 '21 at 19:58
  • What version of R and Shiny are you using that this works? Like I said when I try this it throws an error that crashes the whole app. The workaround I found was using javascript in the UI to catch `shiny:inputchanged` and respond with `Shiny.setInputValue('last_button', event.name+'_'+BUTTON_CLICK_COUNT)` and then setting up an `observeEvent()` with `if (grepl("button[0-9]+",input$last_button)==TRUE)` so it only responds to the desired programmatically defined inputs. Catch is it's laggy and prone to unwanted triggering. – D3SL Apr 05 '21 at 07:11
  • I've updated the answer to use a different list generating method, let me know if it works for you. – Victor Burnett Apr 12 '21 at 01:02
  • I've had to give up on this unfortunately because it kept getting triggered when it wasn't supposed to. I don't believe that's your code however, I was trying to use this to capture clicks on the dynamically produced items in a `shinydashboardplus::dropdownBlock()` and I think something in the `dropdownBlock()` was producing erroneous inputs. – D3SL May 09 '21 at 06:18