<- 1:3
colA <- c("A", "B", "C")
colB
<- function(label) {
makeButton as.character(htmltools::tags$div(htmltools::tags$button(paste(label, "button"))))
}
<- as.character(lapply(c("A", "B", "C"), makeButton))
colC
<- data.frame(A = colA, B = colB, C = colC)
dataFrame ::reactable(dataFrame,
reactablecolumns = list(C = reactable::colDef(sortable = FALSE,
html = TRUE)))
How to render and use buttons in tables is a relatively common task faced by {shiny}
developers. Commonly, buttons in tables are used for getting more details about a row of data, for opening modal panels for user input, for displaying charts, and of course, for navigation. There are many ways to achieve each of these tasks and many tools to do it with. We can create an HTML table containing buttons from scratch (e.g. here), or we can use table widgets like the ones provided by the {reactable}
, {DT}
and many other packages. In this post, we’ll introduce a very straightforward way to implement buttons and use them for navigation with the {reactable}
package. Most of what you’ll see below is also documented in or derived from the reactable
docs.
What if we used modules?
In a realistic app, we would probably use shiny modules. We could easily construct a scenario where the reactable
is rendered through a module, or where each tab is a separate module. The pattern we described above works in this case too, except, with modules, we have keep track of the namespaces. Modules are isolated, so an input value updated in module X will not be known to module Y. We have to implement some way of communication between modules.
Below is one of several ways to send a message from one module to another. In this, case we pass a value from a ‘submodule’ to the calling module. This is a common case, for example, we have the main app server with top-level navigation, that is calling modules for ‘pages’ within the application. The key is to return the input updated by the submodule as a reactive that can be observed in the top-level module.
<- LETTERS[1:3]
tab_names
<- function(nav_id, nav_value) {
buttonSetInput as.character(htmltools::tags$div(htmltools::tags$button(
paste("Go to tab", nav_value),
onClick = sprintf(
"Shiny.setInputValue('%s', '%s', {priority: 'event'})",
nav_id,
nav_value
)
)))
}
<- function(nav_id, nav_value) {
buttonWithAlert as.character(htmltools::tags$div(htmltools::tags$button(
paste("Alert", nav_value),
onClick = sprintf("alert('Nav id is: %s, and nav value is: %s')", nav_id, nav_value)
)))
}
<- function(id) {
rTabUI <- NS(id)
ns tagList(
reactableOutput(ns("myTab"))
)
}
<- function(id) {
rTabServer moduleServer(
id,function(input, output, session) {
<- session$ns
ns
<- as.character(lapply(tab_names, buttonWithAlert, nav_id = ns("myNav")))
alertButtonsHTML <- as.character(lapply(tab_names, buttonSetInput, nav_id = ns("myNav")))
setInputButtonsHTML
<- reactable::reactable(
tableWithButtons data.frame(
Names = LETTERS[1:3],
Alert = alertButtonsHTML,
SetInput = setInputButtonsHTML
),columns = list(
Alert = reactable::colDef(sortable = FALSE,html = TRUE),
SetInput = reactable::colDef(sortable = FALSE, html = TRUE)
)
)
$myTab <- renderReactable({
output
tableWithButtons
})
return(list(
getButtonValue = shiny::reactive(input$myNav)
))
}
)
}
<- bslib::page_fluid(
ui theme = bslib::bs_theme(version = 5, bootswatch = "flatly"),
title = "Reactable buttons navigation",
::titlePanel("App navigation with buttons in reactable widget"),
shiny::div(
shinyclass = "row",
::div(
shinyclass = "col-4",
helpText("Input from reactable button is:"),
verbatimTextOutput("inputFromTableButton"),
::navs_tab_card(
bslibid = "myTabs",
::nav(title = "Tab X", value = "X", rTabUI("rtab") ),
bslib::nav(title = "Tab A", value = "A", "Contents of tab A"),
bslib::nav(title = "Tab B", value = "B", "Contents of tab B"),
bslib::nav(title = "Tab C", value = "C", "Contents of tab C")
bslib
)
)
)
)
<- function(input, output, session) {
server
<- rTabServer("rtab")
rTabOut
$inputFromTableButton <- renderPrint({
output$getButtonValue()
rTabOut
})
::observeEvent(rTabOut$getButtonValue(), {
shiny::updateTabsetPanel(session = session,
shinyinputId = "myTabs",
selected = rTabOut$getButtonValue())
})
}
::shinyApp(ui, server) shiny
Other ways to implement communication between modules include passing (reactive) values through session$userData
(e.g. here) or updating values in an environment or R6
class passed to each module as an argument (e.g. here).
Summary
In this post we covered some examples of how one could implement navigation between ‘pages’ in a {shiny}
application using buttons in a reactable
. We saw how to generate some buttons, how to update input values on button click, how to listen to changes from the buttons, and how to pass the user selections between modules.