How to Disable All Action Buttons While Shiny Is Busy and Loading Text Is Displayed

How can I disable all action buttons while shiny is busy and loading text is displayed

I am not familiar with a simple way to do what you are describing, but of course that does not mean there is none ;) Here is a little workaround that I believe matches your requirements, and keeps your code relatively clean. We can use reactiveValuesToList(input) to get a list of our inputs, and then write a function that disables or enables them all. We can also decide to only toggle button inputs by subsetting the list based on attributes.

Working example below, hope this helps!


Sample Image


library(shiny)
library(shinyjs)

ui <- fluidPage(
h3('Disable all inputs while running'),
actionButton('btn_all_inputs','Run long process'),
h3('Disable only buttons while running'),
actionButton('btn_only_buttons','Run long process'),
hr(),
h3('Inputs'),
textInput('text1', 'Text1',"my text:"),
actionButton('btn1','Button 1'),
actionButton('btn2','Button 2'),
actionButton('btn3','Button 3'),
sliderInput('slid3','Slider 1',min=0,max=1,value=0.5),
useShinyjs()
)

server <- function(input, output, session){

# Function to toggle input elements.
# input_list: List of inputs, reactiveValuesToList(input)
# enable_inputs: Enable or disable inputs?
# Only buttons: Toggle all inputs, or only buttons?
toggle_inputs <- function(input_list,enable_inputs=T,only_buttons=FALSE)
{
# Subset if only_buttons is TRUE.
if(only_buttons){
buttons <- which(sapply(input_list,function(x) {any(grepl('Button',attr(x,"class")))}))
input_list = input_list[buttons]
}

# Toggle elements
for(x in names(input_list))
if(enable_inputs){
shinyjs::enable(x)} else {
shinyjs::disable(x) }
}

observeEvent(input$btn_all_inputs,{
input_list <- reactiveValuesToList(input)
toggle_inputs(input_list,F,F)
Sys.sleep(5)
toggle_inputs(input_list,T,F)
})

observeEvent(input$btn_only_buttons,{
input_list <- reactiveValuesToList(input)
toggle_inputs(input_list,F,T)
Sys.sleep(5)
toggle_inputs(input_list,T,T)
})
}

shinyApp(ui = ui, server = server)

Alternative solution

This solution uses custom JavaScript to enable/disable all inputs based on if Shiny is busy or idle. This will thus disable your inputs anytime Shiny is busy. I now set the script to disable all buttons, but you can easily extend it by adding more selections to document.getElementsByTagName(). Hope this comes closer to what you had in mind.

library(shiny)

ui <- fluidPage(
h3('Disable buttons while running'),
actionButton('btn_run','Run long process'),
hr(),
h3('Inputs'),
textInput('text1', 'Text1',"my text:"),
actionButton('btn1','Button 1'),
sliderInput('slid3','Slider 1',min=0,max=1,value=0.5),
includeScript('script.js')
)

server <- function(input, output, session){

observeEvent(input$btn_run,{
Sys.sleep(5)
})
}

shinyApp(ui = ui, server = server)

script.js

$(document).on("shiny:busy", function() {
var inputs = document.getElementsByTagName("button");
console.log(inputs);
for (var i = 0; i < inputs.length; i++) {
inputs[i].disabled = true;
}
});

$(document).on("shiny:idle", function() {
var inputs = document.getElementsByTagName("button");
console.log(inputs);
for (var i = 0; i < inputs.length; i++) {
inputs[i].disabled = false;
}
});

Disable elements when Shiny is busy

I cannot help you with the close button, though I found out that as soon as you set your shiny:idle handle, any call to JavaScript will fire shiny:idle and hence runs the handler instead of the JavaScript code behind toggleDropdownButton.

However, the first question of how to select more than one element in your JavaScript can be solved with a bit of jQuery. Change your code to

$(document).on('shiny:busy', function() {
var $inputs = $('button,input');
console.log($inputs);
$inputs.prop('disabled', true);
});

$(document).on('shiny:idle', function() {
var $inputs = $('button,input');
console.log($inputs);
$inputs.prop('disabled', false);
})

With that you can select buttons and the text input. Now you can find out yourself which code to use to also disable the dropdown.

BTW: maybe you want to look at shinyjs::disable. With this function you can disable your controls from the R side. You would put that in the beginning of your long calculation and use shinyjs::enable at the end.

How to prevent user from doing anything on shiny app when app is busy

After analyzing yours answers and think more about it, I think I found the simpliest solution.

On "shiny busy" event, I display a div in the conditional panel which is 100% of the screen and on first plan, so it prevents any click on the inputs / outputs behind it. When the app is not busy anymore, the panel disappear. The panel is transparent so the user doesn't see it.

Also, it enables me to disable all inputs and output without being dependant of a timer, only on if the app is busy or not.

library(shiny)

ui <- fluidPage(

# spinner css
tags$head(
tags$style(HTML("
#loadmessage {
position:fixed; z-index:8; top:50%; left:50%; padding:10px;
text-align:center; font-weight:bold; color:#000000; background-color:#CCFF66;
}

.loader {
position:fixed; z-index:8; border:16px solid #999999;
border-top: 16px solid #8B0000; border-radius: 50%;
width: 120px; height: 120px; top:45%; left:45%;
animation: spin 2s linear infinite;
}

.prevent_click{
position:fixed;
z-index:9;
width:100%;
height:100vh;
background-color: transpare'nt;
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}"))
),

# display load spinner when shiny is busy
conditionalPanel(
condition = "$(\'html\').hasClass(\'shiny-busy\')",
tags$div(class = "loader"),
tags$div(class = "prevent_click")
),
actionButton(
inputId = "increment",
label = "Increment"
),
textOutput("result"),
actionButton(
inputId = "busy",
label = "Busy app"
)
)

server <- function(input, output, session) {

rv <- reactiveValues(counter = 0)

#increment counter
observeEvent(input$increment,{
rv$counter = rv$counter + 1
})

#display incremented counter
output$result <- renderText({
rv$counter
})

observeEvent(input$busy, {
Sys.sleep(5)
# during this time, the user should not be able to do anything on the app
})
}

shinyApp(ui = ui, server = server)

Only activate actionButton when Shiny app is not busy

You can disable button after it is clicked and enable it again on exit. It can be done manually but shinyjs already provides required helpers.

If function called on click may fail you can use tryCatch with finally to make sure that your app won't stay in the disabled state:

library(shiny)
library(shinyjs)

foo <- function() {
Sys.sleep(4)
x <- runif(1)
if(x < 0.5) stop("Fatal error")
print(x)
}

shinyApp(
ui=shinyUI(bootstrapPage(
useShinyjs(),
actionButton("go", "GO")
)),
server=shinyServer(function(input, output, session){
observe({
if(input$go == 0) return()
shinyjs::disable("go")

tryCatch(
foo(),
error = function(e) return(),
finally = shinyjs::enable("go")
)
})
})
)

disable actionButton until a new set of options are selected shiny

The observeEvent with the toggleState line is never triggered, which is weird.

It looks like there is an issue with using observeEvent with multiple inputs that are generated by renderUI.

There is a workaround, try using:

observeEvent({
input$SepalLength != NULL |
input$SepalWidth != NULL |
input$Species != NULL
},{
showNotification("triggered")
})

Here is your full code. I used shinyjs to enable/disable the button. In general I would suggest avoiding renderUI unless you can't do without it. You are already using updateSelectInput etc which can handle most things.

library(shiny)
library(vroom)
library(dplyr)
library(shinycssloaders)
library(shinydashboard)
library(shinydashboardPlus)
library(tidyr)
library(shinyjs)

header <- dashboardHeader()

sidebar <- dashboardSidebar(width = 450,
sidebarMenu(id = "tabs",
menuItem(
"Filtros",
tabName = "filtros",
icon = icon("bar-chart-o")
),
uiOutput("filtros")
))

body <- dashboardBody(tabItems(tabItem(tabName = "filtros",
fluidRow(
column(12,
DT::dataTableOutput("tabla_julio") # %>% withSpinner(color = "#0dc5c1")
)
))))

ui <-
dashboardPagePlus(
enable_preloader = FALSE,
sidebar_fullCollapse = TRUE,
header,
sidebar,
body,
useShinyjs()
)

server = function(input, output, session) {

# Create the choices for sample input
vals <- reactiveValues(data = iris, filtered_data = iris)

output$filtros <- renderUI({
datos <- isolate(vals$data)
conditionalPanel(
"input.tabs == 'filtros'",
tagList(
div(
style = "display: inline-block;vertical-align:top; width: 221px;",
numericInput(
inputId = "SepalLength",
label = "Sepal.Length",
value = NA,
min = NA,
max = NA,
step = NA
)
),
div(
style = "display: inline-block;vertical-align:top; width: 221px;",
numericInput(
inputId = "SepalWidth",
label = "Sepal.Width",
value = NA,
min = NA,
max = NA,
step = NA
)
),
div(
div(
style = "display: inline-block;vertical-align:top; width: 224px;",
selectInput(
inputId = "Species",
label = "Species",
width = "220",
choices = unique(isolate(datos$Species)),
selected = NULL,
multiple = TRUE,
selectize = TRUE,
size = NULL
)
)
)
),
actionButton("filtrar", "Filter", style = "width: 100px;"),
actionButton("reset", "Reset", style = "width: 100px;")
)
})

# Filter data
observeEvent(input$filtrar, {
tib <- vals$data

if (!is.na(input$SepalLength)) {
tib <- tib %>% dplyr::filter(Sepal.Length < input$SepalLength)
print(head(tib))
} else {
tib
}
if (!is.na(input$SepalWidth)) {
tib <- tib %>% dplyr::filter(Sepal.Width > input$SepalWidth)
print(head(tib))
} else {
tib
}
# Filter
if (!is.null(input$Species)) {
tib <- tib %>% dplyr::filter(Species %in% input$Species)
} else {
tib
}

print(head(tib, n = 15))

vals$filtered_data <- tib

updateSelectInput(session, inputId = "Species", selected = input$Species, choices = unique(vals$filtered_data$Species))

#Disable filter button
shinyjs::disable("filtrar")

})

observeEvent(input$reset, {
updateNumericInput(session, inputId = "SepalLength", value = NA)
updateNumericInput(session, inputId = "SepalWidth", value = NA)
updateSelectInput(session, inputId = "Species", selected = "")
})

observeEvent({
input$SepalLength != NULL |
input$SepalWidth != NULL |
input$Species!= NULL
},{
shinyjs::enable("filtrar")
})

# Reactive function creating the DT output object
output$tabla_julio <- DT::renderDataTable({
DT::datatable(vals$filtered_data)
}, server = FALSE)

}

shinyApp(ui, server)

How to disable button based on a condition in a R Markdown document with shiny elements?

Took me awhile to find it, but you have to enable shinyjs to use R-markdown explicitly, it needs to setup its javascript differently in this case.

You do this by calling: useShinyjs(rmd=T) in the chunk where you are using it.

---
title: "Disable Button"
runtime: shiny
output: html_document
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(shinyjs)
library(shiny)
```

```{r, echo=FALSE}
useShinyjs(rmd=T)

checkboxGroupInput("param_group", label = h3("Letters"),
choices = LETTERS[1:8])

actionButton('action', "Print")

result<-reactive({
length(input$param_group)
})

observe({
useShinyjs()
if(result()>1 & result()<=5){
enable("action")
} else {
disable("action")
}
})

txt<-eventReactive(input$action,{
cat("Number of letters selected: ",length(input$param_group))
})

renderPrint({
txt()
})

Screen shot:

Sample Image

R shiny: display loading... message while function is running

I'm already using a simpler and more reliable way than the one I posted before.

A combination of

tags$style(type="text/css", "
#loadmessage {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
padding: 5px 0px 5px 0px;
text-align: center;
font-weight: bold;
font-size: 100%;
color: #000000;
background-color: #CCFF66;
z-index: 105;
}
")

with

conditionalPanel(condition="$('html').hasClass('shiny-busy')",
tags$div("Loading...",id="loadmessage")
)

Example:

runApp(list(
ui = pageWithSidebar(
headerPanel("Test"),
sidebarPanel(
tags$head(tags$style(type="text/css", "
#loadmessage {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
padding: 5px 0px 5px 0px;
text-align: center;
font-weight: bold;
font-size: 100%;
color: #000000;
background-color: #CCFF66;
z-index: 105;
}
")),
numericInput('n', 'Number of obs', 100),
conditionalPanel(condition="$('html').hasClass('shiny-busy')",
tags$div("Loading...",id="loadmessage"))
),
mainPanel(plotOutput('plot'))
),
server = function(input, output) {
output$plot <- renderPlot({ Sys.sleep(2); hist(runif(input$n)) })
}
))

tags$head() is not required, but it's a good practice to keep all the styles inside head tag.

Enabling and disabling ActionButton dynamically in RShiny

Updated the code with disable and enable of shinyjs and also to account the condition of work week selection.

 require(shiny)
require(shinyjs)
#install.packages("shinyjs")
ui = fluidPage( useShinyjs(),
inlineCSS(list('.lightpink' = "background-color: lightpink", ".hide1"="display:none",'.red' = "background-color: red", "textarea" = 'text-align: center', '#text3 ' = 'text-align: center', '.form-control' = 'padding:8.5px ')),

fluidRow(
column(3,numericInput("count", "No. of boxes",value = 3, min = 2, max = 10),actionButton("View","view")
),
column(3, actionButton("submit", "Complete"))

),

fluidRow(
column(3,tags$h3("Actual Work Hours")
),
column(3, wellPanel(
numericInput("current", "Current Week",value = 40, min = 40, max = 80)
)),
column(3, wellPanel(
numericInput("next1", "Next Week", value = 40, min = 40, max = 80)
)),
column(3, wellPanel(
numericInput("next2", "Two weeks from now", value = 40, min = 40, max = 80)
))),
fluidRow(
column(3,tags$h3("About Your Work-Week")
),
column(3, wellPanel(
selectizeInput("sel1", "How was your current week?",
choices = c("aa",
"bb",
"cc"),
options = list(
placeholder = "Current week",
onInitialize = I('function() { this.setValue(""); }')
)))),
column(3, wellPanel(
selectizeInput("sel2", "How busy will be the next week?",
choices = c("aa",
"bb",
"cc"),
selected = NULL,
options = list(
placeholder = "Next week",
onInitialize = I('function() { this.setValue(""); }')
)))),
column(3, wellPanel(
selectizeInput("sel3", "How busy will be the next two weeks?",
choices = c("aa",
"bb",
"cc"),
options = list(
placeholder = "Next two week",
onInitialize = I('function() { this.setValue(""); }')
))))),
fluidRow(uiOutput("inputGroup")),
fluidRow(column(3,wellPanel(textOutput("text3")),
tags$head(tags$style("#text3{color: white;
font-style: italic;
}"
)
)))
)
# takes in two arguments
sumN <- function(a, x){
a <- sum(a, as.numeric(x),na.rm=T)
return(a)
}
server <- function(input, output, session) {
Widgets <- eventReactive(input$View,{ input_list <- lapply(1:(input$count),
function(i) {
inputName <- paste("id", i, sep = "")
textInputRow <- function (inputId,value) {
textAreaInput(inputName,"", width = "200px", height = "43px", resize = "horizontal" )
#numericInput(inputName,"",1,0,100)
}
column(4,textInputRow(inputName, "")) })
do.call(tagList, input_list)},ignoreInit = T)
output$inputGroup = renderUI({Widgets()})
getvalues <- reactive({
val <- 0
for(lim in 1:input$count){
observeEvent(input[[paste0("id",lim)]], {
updateTextAreaInput(session,paste0("id",lim), value = ({
x = as.numeric(input[[paste0("id",lim)]])
if(!(is.numeric(x))){0}
else if(!(is.null(x) || is.na(x))){
if(x < 0){
0
}else if(x > 100){
100
} else{
return (isolate(input[[paste0("id",lim)]]))
}
}
else if((is.null(x) || is.na(x))){
0
}
})
)
})
req(as.numeric(input[[paste0("id",lim)]]) >= 0 & as.numeric(input[[paste0("id",lim)]]) <= 100)
val <- sumN(val,as.numeric(input[[paste0("id",lim)]]))
}
val
})
output$text3 <- renderText({
getvalues()
})
observeEvent(getvalues(), {
nn <- getvalues()
if(is.numeric(as.numeric(nn)) & !is.na(as.numeric(nn)) & nn == 100) {

removeClass("text3", 'red')
addClass('text3','lightpink')
if(input$sel1 != "" & input$sel2 != "" & input$sel3 != "") {

enable('submit')
}

#removeClass('submit','hide1')

} else { addClass('text3','red'); #addClass('submit','hide1');
disable('submit')
}
})
}
shinyApp(ui=ui, server = server)


Related Topics



Leave a reply



Submit