My first serious Shiny project is finished: 4517 lines of code (without comments)! Now I’m taking the time to go through the code again and reflect. What has turned out well and can be continued in the future? What are problem areas and need to be reworked?
I share my thoughts here in a series of articles, also to sort them out and not to forget them. This first article is about buttons.
Vertical alignment
Often my button is placed to the right of an input box, slider or similar. The other input elements often have a label, the button does not and is then vertically shifted:
On stackoverflow I asked how to fix this and quickly got a solution:
shiny::column(2,
shiny::actionButton(ns("dbconnect"), "Connect!"),
style = "margin-top:25px;" ## <-- !
)
Warnings and Error Messages in Shiny
I want the code that actually does something (hereafter: working code) to be separate from Shiny. That means I don’t want any calls to showNotification
in my working code. However, I still want to use the classic signal functions of R – message
, warning
and stop
– in my working functions.
On Stackoverflow I found a solution for this and defined it as the function exec_safely
. The function is stored here. It is a decorator, where an expression is executed with tryCatch
and withCallingHandlers
.
Now when I write an event handler for a button, I use exec_safely
:
rv <- shiny::reactiveValues()
shiny::observeEvent(input$btn, exec_safely(session,{
rv[["log_x"]] <- log(as.character(input$x))
})
)
The sample app demonstrates the behaviour. To do this, the code at the link above must be read in, e.g. via source("https://pastebin.com/3RURswkd")
. Then both the functions exec_safely
and demo_exec_safely
are defined. The app is started via demo_exec_safely()
.
Negative values should produce a warning and non-numeric values an error and display in the web interface.
Modal Confirmation Dialog
Before deleting, I want to show the user a modal dialogue and give them a chance to change their mind.
I found a solution to this on stackoverflow as well, but can’t find the post about it anymore. I use the code as a pattern, wrapping it in a function requires too many parameters and doesn’t increase readability. Here is an example:
ns <- session$ns
# remove button pressed?
shiny::observeEvent(input$delete_button, {
shiny::showModal(shiny::modalDialog(
title="Are you sure?",
"Deleting is irreversible.",
footer = shiny::tagList(
shiny::actionButton(ns("delete_confirmed"), "Delete!"),
shiny::modalButton("Cancel")
)
))
})
shiny::observeEvent(input$delete_confirmed, exec_safely(parent, {
shiny::removeModal()
# do it...
message("Deletion complete.")
}))
Note that I’m using Shiny modules, so I need to wrap the id
of the “Confirm” button in the namespace function ns
.
Summary
Regarding the implementation of buttons in my first larger Shiny project, I am satisfied. There are still some code places where I proceeded differently than described here, this still needs to be adjusted and updated.
Share your thoughts on this article on Twitter!