A little over two years ago I asked a question on StackOverflow about how to attach multiple buttons to one observer. There were no good answers then, but after reading a pretty good JavaScript & jQuery book, I finally figured it out.
Why do I need this? Here's an example:
This is a list of projects with buttons to view or join any project. When setting up the observer(s) for these buttons, I have no idea how many projects there will be, but there could be hundreds. With actionButton() you need one observer for each button, which could lead to madness.
I finally solved this problem with a JavaScript/jQuery function that sets an on click event on all the buttons in a document. When a button is clicked, the function uses Shiny.onInputChange()
to send the id (<button id="xxx"...
) of the button that was clicked to the observer.
You can see the details with code examples at One observer for all buttons in Shiny using JavaScript/jQuery.
3 Likes
Here's an option that works. It's a bit of a brute force implementation, but has worked well for me in my applications. The magic happens in the section of code marked by # One observer to rule them all
.
The key feature of this is that you pass the list of potential buttons into an lapply
where the FUN
argument contains an observeEvent
call.
library(shiny)
library(dplyr)
shinyApp(
ui =
fluidPage(
# Display the row for the button clicked
verbatimTextOutput("details"),
fluidRow(
column(width = 3,
selectInput(inputId ="data_source",
label = "Select a data set",
choices = c("mtcars", "iris"))),
column(width = 9,
uiOutput("show_table"))
)
),
server =
shinyServer(function(input, output, session){
# Store the details of the clicked row
Data <- reactiveValues(
Info = NULL
)
# One Observer to Rule Them All (evil cackle)
# Update the Data$Info value.
observe({
# Identify all of the buttons in the table.
# Note that I assumed the same prefix on all buttons, and
# they only differ on the number following the underscore
# This must happen in an observed since the number of rows
# in the table is not fixed.
input_btn <- paste0("btn_", seq_len(nrow(display_table())))
lapply(input_btn,
function(x){
observeEvent(
input[[x]],
{
i <- as.numeric(sub("btn_", "", x))
Data$Info <- display_table()[i, -length(display_table())]
}
)
})
})
# Generate the table of data.
display_table <-
reactive({
tbl <-
get(input$data_source) %>%
# Add the row names as a column (not always useful)
cbind(row_id = rownames(.),
.) %>%
# Add the action buttons as the last column
mutate(button = vapply(row_number(),
function(i){
actionButton(inputId = paste0("btn_", i),
label = "View Details") %>%
as.character()
},
character(1)))
})
# Render the table with the action buttons
output$show_table <-
renderUI({
display_table() %>%
select(row_id, button) %>%
knitr::kable(format = "html",
# very important to use escape = FALSE
escape = FALSE) %>%
HTML()
})
# Print the details to the screen.
output$details <-
renderPrint({
req(Data$Info)
Data$Info
})
})
)
1 Like
You've gotten a lot further than I ever did on programatically creating the required number of observeEvents()
!
Before figuring out the JavaScript solution, I was using copy/paste-paste-paste... to create 100 observeEvents()
and limited the table to 100 rows using pagination. Pagination turns out to be a lot more complicated than it looks, however, and both our solutions are better than that.
1 Like