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

RShiny

R Problem Overview


I use Shiny GUI R package. I'm looking for a way to display a message like "loading..." after the actionButton was pressed. The function takes several minutes to execute, so I need to inform the user somehow that the button actually triggered some event. Now the server.R code looks like this:

DATA <- reactive({
  if(input$DownloadButton>0) {
    RunDownload()
  } else {
    NULL
  }
})

output$Download <- renderText({
  if(NROW(DATA())>0) {
    paste0(Sys.time(),": ",NROW(DATA()), " items downloaded")
  } else {
    ''
  }
})

actionButton() is a function that downloads data from internet. input$DownloadButton is actionButton. So after the button was pressed the user waits for several minutes and only then sees a message saying that download was successful. I would like to show a message "Loading..." just after the actionButton was pressed and then another message like paste0(Sys.time(),": ",NROW(DATA()), " items downloaded") when execution ends.

R Solutions


Solution 1 - R

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.

Solution 2 - R

Very simply, you can use built-in shiny functions showModal() at the start of the function and removeModal() at the end. If you remove the footer, said modal cannot be clicked out of.

Example:

observeEvent(input$button, {
     showModal(modalDialog("Doing a function", footer=NULL))
     #Do the stuff here....
     #...
     #...
     #Finish the function
     removeModal()
})

Solution 3 - R

I solved the problem by adding the following code to sidebarPanel():

HTML('<script type="text/javascript">
        $(document).ready(function() {
          $("#DownloadButton").click(function() {
            $("#Download").text("Loading...");
          });
        });
      </script>
')

Solution 4 - R

You can use ShinyJS: https://github.com/daattali/shinyjs

When the actionButton is pressed, you can easily toggle a text component showing "loading...", and when the calculation is finished, you can then toggle this component to hidden.

Solution 5 - R

Though this question is old I think it is still relevant. I have another solution to offer that displays the activity indicator on the button that starts a lengthy process next to the button label.

Button with an activity indicator

We need an action button with a label in a span and some way of identifying that label.

actionButton("btnUpdate", span("Update", id="UpdateAnimate", class=""))

We also need some CSS animation that can be added to the button label, e.g. like this:

            tags$head(tags$style(type="text/css", '
            .loading {
                display: inline-block;
                overflow: hidden;
                height: 1.3em;
                margin-top: -0.3em;
                line-height: 1.5em;
                vertical-align: text-bottom;
                box-sizing: border-box;
            }
            .loading.dots::after {
                text-rendering: geometricPrecision;
                content: "⠋\\A⠙\\A⠹\\A⠸\\A⠼\\A⠴\\A⠦\\A⠧\\A⠇\\A⠏";
                animation: spin10 1s steps(10) infinite;
                animation-duration: 1s;
                animation-timing-function: steps(10);
                animation-delay: 0s;
                animation-iteration-count: infinite;
                animation-direction: normal;
                animation-fill-mode: none;
                animation-play-state: running;
                animation-name: spin10;
            }
            .loading::after {
                display: inline-table;
                white-space: pre;
                text-align: left;
            }
            @keyframes spin10 { to { transform: translateY(-15.0em); } }
            '))

Now we can use shinyjsto manipulate the span class which dynamically adds the animation behind the button label. We add the animation once a user presses the button:

    observeEvent(input$btnUpdate, { # User requests update
        # ... 

        shinyjs::addClass(id = "UpdateAnimate", class = "loading dots")
        shinyjs::disable("btnUpdate")
        
        # ...
    })

When the operation has finished we can remove the class from the span and end the animation:

    output$distPlot <- renderPlot({
        # ...
        
        Sys.sleep(1) # just for show, you probably want to remove it in a real app
        # Button settings        
        shinyjs::enable("btnUpdate")
        shinyjs::removeClass(id = "UpdateAnimate", class = "loading dots")

        # ...
    })

The full code of the sample app is available as gist on GitHub.

Solution 6 - R

I found a solution, that works fine for me. I am using the Bootstrap modal. It is shown when the execution of the function starts and is hidden again, when it ends.

> modalBusy <- function(id, title, ...){ > > msgHandler = singleton(tags$head(tags$script('Shiny.addCustomMessageHandler("jsCode", > function(message) { > console.log(message) > eval(message.code); > });' > ) > ) > ) > > label_id = paste(id, "label", sep='-') > modal_tag <- div(id=id, > class="modal hide fade", > "aria-hidden"=FALSE, > "aria-labelledby"=label_id, > "role"="dialog", > "tabindex"="-1", > "data-keyboard"=FALSE, > "data-backdrop"="static") > header_tag <- div(class="modal-header", > h3(id=label_id, title)) > body_tag <- div(class="modal-body", > Row(...))
> footer_tag <- div(class="modal-footer") > modal_tag <- tagAppendChildren(modal_tag, header_tag, body_tag, footer_tag) > tagList(msgHandler, modal_tag) > }

To show and to hide it use the functions

showModal <- function(id,session) {
  session$sendCustomMessage(type="jsCode",
                            list(code= paste("$('#",id,"').modal('show')"
                                             ,sep="")))
}

hideModal <- function(id,session) {
  session$sendCustomMessage(type="jsCode",
                            list(code= paste("$('#",id,"').modal('hide')"
                                             ,sep="")))
}

Call the showModal function before your function Call and the hideModal function afterwards!

Hope this helps.

Seb

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser1603038View Question on Stackoverflow
Solution 1 - Ruser1603038View Answer on Stackoverflow
Solution 2 - Rmoman822View Answer on Stackoverflow
Solution 3 - Ruser1603038View Answer on Stackoverflow
Solution 4 - RzhanxwView Answer on Stackoverflow
Solution 5 - RJanView Answer on Stackoverflow
Solution 6 - RSebView Answer on Stackoverflow