0

I would like to disable menu items and selectInput, while my Shiny app is loading. I have managed to disable buttons and textInput with some javacript (cf. Disable elements when Shiny is busy), but I can't get it to work with selectInput and menus. I'm not interested in alternatives solutions.

library(shiny)

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

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


ui <- fluidPage(

  titlePanel("Hello Shiny!"),

  sidebarLayout(
    sidebarPanel(
      tags$head(tags$script(js)),
      navlistPanel(
        tabPanel("Component 1"),
        tabPanel("Component 2")
      )
    ),
    mainPanel(
      actionButton("buttonID","This adds 10 seconds of Sys.sleep"),
      textInput("textID","Write text here..."),
      selectInput("selectID","This should be disables while loading",choices=c("A","B","C"))
    )
  )
)

server <- function(input, output) { 
  observeEvent(input$buttonID,{
    Sys.sleep(10) 
  })
}

shinyApp(ui, server)
Tobias D
  • 345
  • 2
  • 12

2 Answers2

3

Theres easier way of disabling widgets using shinyjs package. theres a reactiveValuesToList function which will collect all the reactivesinputs you have within the session and you can simply use that:

library(shiny)
library(shinyjs)

ui <- fluidPage(

  titlePanel("Hello Shiny!"),
  useShinyjs(), 
  sidebarLayout(
    sidebarPanel(
      navlistPanel(
        tabPanel("Component 1"),
        tabPanel("Component 2")
      )
    ),
    mainPanel(
      actionButton("buttonID","This adds 5 seconds of Sys.sleep"),
      textInput("textID","Write text here..."),
      selectInput("selectID","This should be disables while loading",choices=c("A","B","C"))
    )
  )
)

server <- function(input, output) { 

  observeEvent(input$buttonID,{
    myinputs <- names(reactiveValuesToList(input))
    print(myinputs)
    for(i in 1:length(myinputs)){
      disable(myinputs[i])
    }
    Sys.sleep(5) 
    for(i in 1:length(myinputs)){
      enable(myinputs[i])
    }
  })
}

shinyApp(ui, server)

enter image description here

Pork Chop
  • 28,528
  • 5
  • 63
  • 77
  • I see that my simplified example is not adequate. I have a rather complex application with many processes that take different time to run. I could use shinyjs in each reactive, but since I have many reactives, this is not very elegant. Therefore, I prefer a javescript solution. – Tobias D Oct 08 '18 at 08:56
  • See the update on how to collect all the inputs and disable, enable those – Pork Chop Oct 08 '18 at 09:08
  • This is of course better, but I still need to put the shinyjs code in each reactive, that loads data, and since I have many reactives, this is not very elegant. – Tobias D Oct 08 '18 at 09:26
  • I'm sure I can help you if you post your entire code. Note that proprietary information excuse isn't acceptable as you can always create dummy variable with data – Pork Chop Oct 08 '18 at 10:08
  • Thanks for trying to help. However, I think my question is pretty clear: I would like to get a javascript solution, as my example, which works partly. Nevertheless, I should have stressed that I don't want alternative solutions. My application is about 3000 lines of code, so I can't post that. I have maybe 20 reactives, which all gives busy time. If I should use shinyjs, I would have to add the shinyjs code in all the reactives, which can be done, but is not as elegant as the javascript code. – Tobias D Oct 08 '18 at 10:53
0

ANSWER A)

The simple answer to your question is to set selectize = FALSE in your selectInput.

In the shiny docs, it's stated that the selectInput function uses the selectize.js JavaScript library by default (see below).

"By default, selectInput() and selectizeInput() use the JavaScript library selectize.js (https://github.com/brianreavis/selectize.js) to instead of the basic select input element. To use the standard HTML select input element, use selectInput() with selectize=FALSE."

By setting selectize = FALSE you are instead using the standard HTML select input element. This, in turn, is now picked up by your jquery var $inputs = $('button,input,select,ul');. I'm not sure why the element is not picked up when using the selectize.js library.

See the below example. Note that the selectInput options look different when using the html standard (not as nice aesthetically imo).

# ANSWER A)
library(shiny)

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

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


ui <- fluidPage(

  titlePanel("Hello Shiny!"),

  sidebarLayout(
    sidebarPanel(
      tags$head(tags$script(js)),
      navlistPanel(
        tabPanel("Component 1"),
        tabPanel("Component 2")
      )
    ),
    mainPanel(
      actionButton("buttonID", "This adds 3 seconds of Sys.sleep"),
      textInput("textID", "Write text here..."),
      selectInput("selectID", "This should be disables while loading", choices=c("A","B","C"), selectize = FALSE)
    )
  )
)

server <- function(input, output) { 
  observeEvent(input$buttonID,{
    Sys.sleep(3) 
  })
}

shinyApp(ui, server)

ANSWER B)

Below is how I got the selectize.js selectInput to disable when shiny is busy, as per your question, using the conditionalPanel. It's a bit of a hacky solution, but works well.

Note that I am creating two selectInputs which are similar. One is initialised as disabled using the shinyjs package. This is the one that is displayed when shiny is busy. Using jquery snippet $('html').hasClass('shiny-busy') we make use of the shiny-busy class applied when shiny is busy performing logic on the server side. When this class is removed (i.e. when shiny is idle) the conditionalPanel swaps in the other selectInput UI element that is not disabled.

# EXAMPLE B)
library(shiny)
library(shinyjs)

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

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


ui <- fluidPage(

  titlePanel("Hello Shiny!"),

  sidebarLayout(
    sidebarPanel(
      tags$head(tags$script(js)),
      navlistPanel(
        tabPanel("Component 1"),
        tabPanel("Component 2")
      )
    ),
    mainPanel(
      useShinyjs(),
      actionButton("buttonID", "This adds 3 seconds of Sys.sleep"),
      textInput("textID", "Write text here..."),
      #selectInput("selectID", "This should be disables while loading", choices=c("A","B","C"), selectize = FALSE)
      div(
      conditionalPanel(
        condition="$('html').hasClass('shiny-busy')",
        shinyjs::disabled(
            selectInput(
            inputId = 'selectID_disabled', # note the addition of "_disabled" as IDs need to be unique
            label = "This should be disables while loading",
            choices = "Loading...",
            selected = NULL,
            selectize = TRUE,
            width = 250,
            size = NULL
          )
        )
      ),
      conditionalPanel(
        condition="$('html').hasClass('')", 
        selectInput(
          inputId = 'selectID',
          label = "This should be disables while loading",
          choices = c("A","B","C"),
          selected = NULL,
          selectize = TRUE,
          width = 250,
          size = NULL
        )
      ),
      style = "margin-top:20px;"
    ),
    )
  )
)

shinyServer(function(input, output, session) {
  observeEvent(input$buttonID,{
    Sys.sleep(3) 
  })
})

shinyApp(ui, server)
BilboBaagins
  • 66
  • 1
  • 4