Shiny App Ask User Browse a File and Show Error if Uploaded File Is Not Correct
User feedback
You can oft make your app more usable past giving the user more insight into what is happening. This might take the form of ameliorate messages when inputs don't make sense, or progress bars for operations that take a long fourth dimension. Some feedback occurs naturally through outputs, which you already know how to employ, but you'll often need something else. The goal of this chapter is to show you some of your other options.
We'll start with techniques for validation, informing the user when an input (or combination of inputs) is in an invalid state. We'll then continue on to notification, sending general messages to the user, and progress bars, which give details for fourth dimension consuming operations fabricated upward of many small steps. We'll finish up past discussing dangerous actions, and how y'all requite your users peace of listen with confirmation dialogs or the ability to disengage an action.
In this chapter we'll employ shinyFeedback, by Andy Merlino, and waiter, past John Coene. You should also go on your eyes open up for shinyvalidate, a package past Joe Cheng, that is currently under evolution.
Validation
The first and nearly important feedback y'all can give to the user is that they've given you bad input. This is analogous to writing good functions in R: user-friendly functions give clear error messages describing what the expected input is and how you accept violated those expectations. Thinking through how the user might misuse your app allows you lot to provide informative messages in the UI, rather than allowing errors to trickle through into the R code and generate uninformative errors.
Validating input
A great fashion to give boosted feedback to the user is via the shinyFeedback parcel. Using it is a two step process. First, you lot add useShinyFeedback()
to the ui
. This sets upward the needed HTML and JavaScript for bonny error message display:
Then in your server()
role, you lot call one of the feedback functions: feedback()
, feedbackWarning()
, feedbackDanger()
, and feedbackSuccess()
. They all have three key arguments:
-
inputId
, the id of the input where the feedback should be placed. -
testify
, a logical determining whether or not to show the feedback. -
text
, the text to brandish.
They also accept color
and icon
arguments that you tin can use to further customise the appearance. Encounter the documentation for more details.
Lets see how this comes together in a real example, pretending that we merely want to allow fifty-fifty numbers. Effigy 8.1 shows the results.
server <- function ( input, output, session ) { half <- reactive ( { fifty-fifty <- input $ n %% ii == 0 shinyFeedback :: feedbackWarning ( "n", ! even, "Delight select an even number" ) input $ northward / ii } ) output $ one-half <- renderText ( half ( ) ) }
Find that the error message is displayed, only the output is still updated. Typically yous don't want that because invalid inputs are likely to cause uninformative R errors that you don't want to show to the user. To cease inputs from triggering reactive changes, yous need a new tool: req()
, brusk for "required". Information technology looks like this:
server <- function ( input, output, session ) { half <- reactive ( { even <- input $ north %% 2 == 0 shinyFeedback :: feedbackWarning ( "n", ! even, "Please select an even number" ) req ( even ) input $ n / 2 } ) output $ half <- renderText ( half ( ) ) }
When the input to req()
is non true, it sends a special signal to tell Shiny that the reactive does non have all the inputs that it requires, so it should be "paused". We'll accept a cursory digression to talk about this before we come back to using it in concert with validate()
.
Cancelling execution with req()
It'south easiest to sympathize req()
past starting outside of validation. You lot may take noticed that when you beginning an app, the complete reactive graph is computed even before the user does annihilation. This works well when you lot can choose meaningful default value
southward for your inputs. Merely that's non always possible, and sometimes you want to wait until the user actually does something. This tends to crop up with 3 controls:
-
In
textInput()
, you've usedvalue = ""
and yous don't want to do anything until the user types something. -
In
selectInput()
, you've provide an empty pick,""
, and you don't want to do anything until the user makes a selection. -
In
fileInput()
, which has an empty consequence earlier the user has uploaded anything. We'll come back to this in Section 9.1.
We need some way to "pause" reactives so that nothing happens until some condition is true. That's the job of req()
, which checks for required values before assuasive a reactive producer to go on.
For example, consider the following app which will generate a greeting in English language or Maori. If you run this app, you'll run into an error, equally in Effigy viii.two, because there's no entry in the greetings
vector that corresponds to the default choice of ""
.
ui <- fluidPage ( selectInput ( "language", "Language", choices = c ( "", "English", "Maori" ) ), textInput ( "name", "Name" ), textOutput ( "greeting" ) ) server <- part ( input, output, session ) { greetings <- c ( English language = "Howdy", Maori = "Kia ora" ) output $ greeting <- renderText ( { paste0 ( greetings [[ input $ language ] ], " ", input $ proper noun, "!" ) } ) }
We can set this problem past using req()
, as below. Now nothing volition be displayed until the user has supplied values for both language and name, every bit shown in Figure 8.three.
server <- function ( input, output, session ) { greetings <- c ( English = "Hello", Maori = "Kia ora" ) output $ greeting <- renderText ( { req ( input $ language, input $ proper noun ) paste0 ( greetings [[ input $ language ] ], " ", input $ name, "!" ) } ) }
req()
works past signalling a special status 27. This special condition causes all downstream reactives and outputs to stop executing. Technically, it leaves whatsoever downstream reactive consumers in an invalidated state. We'll come back to this terminology in Chapter 16.
req()
is designed so that req(input$x)
will only proceed if the user has supplied a value, regardless of the type of the input control28. You tin can likewise use req()
with your own logical argument if needed. For instance, req(input$a > 0)
will permit computation to go on when a
is greater than 0; this is typically the grade you'll apply when performing validation, every bit we'll see next.
req()
and validation
Let's combine req()
and shinyFeedback to solve a more challenging trouble. I'one thousand going to return to the simple app we made in Chapter ane which immune you lot to select a built-in dataset and see its contents. I'm going to make information technology more than general and more complex by using textInput()
instead of selectInput()
. The UI changes very little:
Just the server role needs to become a lilliputian more complex. We're going to use req()
in two ways:
-
We only want to go along with computation if the user has entered a value and so we do
req(input$dataset)
. -
And so we bank check to run across if the supplied proper noun really exists. If information technology doesn't, we display an error bulletin, and then use
req()
to abolish computation. Note the use ofcancelOutput = TRUE
: normally cancelling a reactive will reset all downstream outputs; usingcancelOutput = TRUE
leaves them displaying the last good value. This is important fortextInput()
which may trigger an update while you lot're in the center of typing a name.
The results are shown in Figure 8.4.
server <- function ( input, output, session ) { data <- reactive ( { req ( input $ dataset ) exists <- exists ( input $ dataset, "packet:datasets" ) shinyFeedback :: feedbackDanger ( "dataset", ! exists, "Unknown dataset" ) req ( exists, cancelOutput = TRUE ) become ( input $ dataset, "bundle:datasets" ) } ) output $ data <- renderTable ( { caput ( data ( ) ) } ) }
Validate output
shinyFeedback is neat when the trouble is related to a single input. But sometimes the invalid state is a result of a combination of inputs. In this instance it doesn't really make sense to put the mistake next to an input (which i would you put it beside?) and instead it makes more sense to put it in the output.
You tin do so with a tool built into shiny: validate()
. When chosen within a reactive or an output, validate(message)
stops execution of the residual of the code and instead displays message
in any downstream outputs. The following lawmaking shows a elementary example where we don't desire to log or square-root negative values. You tin can see the results in Figure 8.5.
ui <- fluidPage ( numericInput ( "x", "x", value = 0 ), selectInput ( "trans", "transformation", choices = c ( "square", "log", "foursquare-root" ) ), textOutput ( "out" ) ) server <- part ( input, output, session ) { output $ out <- renderText ( { if ( input $ 10 < 0 && input $ trans %in% c ( "log", "foursquare-root" ) ) { validate ( "x can not exist negative for this transformation" ) } switch ( input $ trans, square = input $ ten ^ 2, "square-root" = sqrt ( input $ 10 ), log = log ( input $ ten ) ) } ) }
Notifications
If in that location isn't a problem and you just want to let the user know what's happening, and then yous want a notification. In Shiny, notifications are created with showNotification()
, and stack in the bottom right of the folio. There are 3 basic ways to use showNotification()
:
-
To bear witness a transient notification that automatically disappears after a stock-still amount of time.
-
To show a notification when a process starts and remove information technology when the procedure ends.
-
To update a single notification with progressive updates.
These three techniques are discussed below.
Transient notification
The simplest style to use showNotification()
is to call information technology with a single argument: the message that you desire to brandish. It'southward very hard to capture this behaviour with a screenshot, so go to https://hadley.shinyapps.io/ms-notification-transient if you want to see it in action.
By default, the message will disappear after 5 seconds, which you can override by setting elapsing
, or the user can dismiss it earlier by clicking the close push. If y'all want to make the notification more prominent, you can set the type
statement to one of "message", "warning", or "error". Figure viii.six gives a sense of what this looks like.
Removing on completion
It'due south often useful to tie the presence of a notification to a long-running task. In this case, you want to show the notification when the task starts, and remove the notification when the job completes. To practise this, you'll need to:
-
Set
elapsing = Goose egg
andcloseButton = FALSE
then that the notification stays visible until the task is consummate. -
Store the
id
returned byshowNotification()
, and then pass this value toremoveNotification()
. The about reliable way to do and so is to employon.exit()
, which ensures that the notification is removed regardless of how the task completes (either successfully or with an error). You can learn more virtuallyon.get out()
in Changing and restoring country.
The following case puts the pieces together to prove how you might keep the user up to engagement when reading in a large csv file29:
Generally, these sort of notifications volition live in a reactive, considering that ensures that the long-running computation is only re-run when needed.
Progressive updates
As yous saw in the first case, multiple calls to showNotification()
usually create multiple notifications. You can instead update a single notification by capturing the id
from the first call and using it in subsequent calls. This is useful if your long-running task has multiple subcomponents. Y'all tin can see the results in https://hadley.shinyapps.io/ms-notification-updates.
ui <- fluidPage ( tableOutput ( "data" ) ) server <- role ( input, output, session ) { notify <- role ( msg, id = Naught ) { showNotification ( msg, id = id, elapsing = Cypher, closeButton = FALSE ) } data <- reactive ( { id <- notify ( "Reading data..." ) on.exit ( removeNotification ( id ), add = Truthful ) Sys.sleep ( i ) notify ( "Reticulating splines...", id = id ) Sys.sleep ( 1 ) notify ( "Herding llamas...", id = id ) Sys.sleep ( i ) notify ( "Orthogonalizing matrices...", id = id ) Sys.slumber ( ane ) mtcars } ) output $ data <- renderTable ( head ( information ( ) ) ) }
Progress bars
For long-running tasks, the best type of feedback is a progress bar. Too as telling yous where you are in the process, it also helps you estimate how much longer it's going to exist: should yous take a deep breath, get get a java, or come back tomorrow? In this section, I'll testify ii techniques for displaying progress confined, one built into Shiny, and one from the waiter package developed by John Coene.
Unfortunately both techniques endure from the same major drawback: to utilise a progress bar you need to be able to split the big task into a known number of small pieces that each take roughly the aforementioned corporeality of fourth dimension. This is frequently hard, particularly since the underlying code is often written in C and it has no way to communicate progress updates to you. We are working on tools in the progress parcel and so that packages like dplyr, readr, and vroom will one twenty-four hour period generate progress bars that you lot can easily forward to Shiny.
Shiny
To create a progress bar with Shiny, yous need to use withProgress()
and incProgress()
. Imagine you take some dull running code that looks like thisthirty:
for ( i in seq_len ( step ) ) { ten <- function_that_takes_a_long_time ( ten ) }
You beginning by wrapping it in withProgress()
. This shows the progress bar when the lawmaking starts, and automatically removes it when it'southward done:
Then call incProgress()
later on each step:
The first argument of incProgress()
is the amount to increment the progress bar. By default, the progress bar starts at 0 and ends at one, so the incrementing by 1 divided by the number of steps will ensure that the progress bar is complete at the end of the loop.
Here's how that might look in a complete Shiny app, as shown in Figure 8.7.
ui <- fluidPage ( numericInput ( "steps", "How many steps?", 10 ), actionButton ( "go", "go" ), textOutput ( "upshot" ) ) server <- function ( input, output, session ) { data <- eventReactive ( input $ go, { withProgress (message = "Computing random number", { for ( i in seq_len ( input $ steps ) ) { Sys.sleep ( 0.five ) incProgress ( one / input $ steps ) } runif ( ane ) } ) } ) output $ effect <- renderText ( circular ( data ( ), 2 ) ) }
A few things to note:
-
I used the optional
message
statement to add together some explanatory text to the progress bar. -
I used
Sys.sleep()
to simulate a long running operation; in your code this would exist a slow role. -
I allowed the user to command when the event starts past combining a button with
eventReactive()
. This is proficient practice for whatever task that requires a progress bar.
Waiter
The congenital-in progress bar is nifty for the basics, but if yous desire something that provides more visual options, you might try the waiter package. Adapting the above code to piece of work with Waiter is straightforward. In the UI, we add use_waitress()
:
The interface for waiter's progress bars are a petty different. The waiter package uses an R6 object to bundle all progress related functions into a unmarried object. If you lot've never used an R6 object before, don't worry besides much virtually the details; you can just copy and paste this template. The basic lifecycle looks like this:
# Create a new progress bar waitress <- waiter :: Waitress $ new (max = input $ steps ) # Automatically close information technology when done on.get out ( waitress $ close ( ) ) for ( i in seq_len ( input $ steps ) ) { Sys.sleep ( 0.5 ) # increase i pace waitress $ inc ( 1 ) }
And we can use it in a Shiny app as follows:
The default display is a thin progress bar at the tiptop of the page (which you can meet https://hadley.shinyapps.io/ms-waiter), but there are a number of ways to customise the output:
-
You can override the default
theme
to use one of:-
overlay
: an opaque progress bar that hides the whole page -
overlay-opacity
: a translucent progress bar that covers the whole page -
overlay-percent
: an opaque progress bar that also displays a numeric percentage.
-
-
Instead of showing a progress bar for the unabridged page, you tin can overlay it on an existing input or output past setting the
selector
parameter, due east.g.:waitress <- Waitress $ new (selector = "#steps", theme = "overlay" )
Spinners
Sometimes you don't know exactly how long an operation will accept, and you simply desire to brandish an blithe spinner that reassures the user that something is happening. You can likewise use the waiter parcel for this task; just switch from using a Waitress
to using a Waiter
:
Similar Waitress
, you can likewise utilize Waiter
s for specific outputs. These waiters can automatically remove the spinner when the output updates, then the code is fifty-fifty simpler:
The waiter bundle provides a large multifariousness of spinners to choose from; run into your options at ?waiter::spinners
and and then choose one with (due east.g.) Waiter$new(html = spin_ripple())
.
An even simpler culling is to utilize the shinycssloaders parcel past Dean Attali. It uses JavaScript to listen to Shiny events, then information technology doesn't fifty-fifty need whatsoever lawmaking on the server side. Instead, you just use shinycssloaders::withSpinner()
to wrap outputs that you want to automatically become a spinner when they have been invalidated.
Confirming and undoing
Sometimes an activity is potentially dangerous, and you either want to make sure that the user really wants to practice information technology, or you lot want to requite them the ability to back out earlier information technology'due south as well belatedly. The 3 techniques in this section lay out your basic options and give you some tips for how you might implement them in your app.
Explicit confirmation
The simplest approach to protecting the user from accidentally performing a dangerous action is to require an explicit confirmation. The easiest way is to use a dialog box which forces the user to pick from i of a small fix of deportment. In Shiny, you create a dialog box with modalDialog()
. This is chosen a "modal" dialog because it creates a new "mode" of interaction; you can't collaborate with the master application until you have dealt with the dialog.
Imagine yous have a Shiny app that deletes some files from a directory (or rows in a database etc). This is difficult to undo so yous want to brand sure that the user is really sure. You could create a dialog box, as shown in Figure eight.ten, that requires an explicit confirmation as follows:
At that place are a few small, simply important, details to consider when creating a dialog box:
-
What should you call the buttons? It's best to be descriptive, so avoid yep/no or continue/cancel in favour of recapitulating the key verb.
-
How should you order the buttons? Do you put cancel outset (similar the Mac), or go on first (like Windows)? Your all-time selection is to mirror the platform that you recollect nearly people volition be using.
-
Tin can you make the unsafe choice more obvious? Hither I've used
class = "btn btn-danger"
to style the button prominently.
Jakob Nielsen has more than good advice at http://www.useit.com/alertbox/ok-cancel.html.
Permit's use this dialog in a real (if very simple) app. Our UI exposes a single push to "delete all the files":
There are 2 new ideas in the server()
:
-
We use
showModal()
andremoveModal()
to show and hibernate the dialog. -
Nosotros observe events generated by the UI from
modal_confirm
. These objects aren't created statically in theui
, merely are instead dynamically added in theserver()
byshowModal()
. You'll come across that idea in much more detail in Affiliate ten.
Undoing an action
Explicit confirmation is most useful for destructive actions that are merely performed infrequently. You should avert it if you want to reduce the errors made by frequent deportment. For example, this technique would not work for twitter — if there was a dialog box that said "Are you sure you desire to tweet this?" you would soon learn to automatically click yes, and withal feel the same feeling of regret when you detect a typo 10s later on tweeting.
In this situation a meliorate arroyo is to wait few seconds earlier really performing the activeness, giving the user a run a risk to discover any bug and undo them. This isn't really an undo (since you're not really doing anything), but it's an evocative word that users will empathise.
I illustrate the technique with a website that I personally wish had an disengage push: Twitter. The essence of the Twitter UI is very simple: there's a text expanse to etch your tweet and a button to send it:
The server function is quite circuitous and requires some techniques that nosotros haven't talked about. Don't worry too much about understanding the code, focus on the basic thought: nosotros utilise some special arguments to observeEvent()
to run some code later on a few seconds. The big new idea is that we capture the result of observeEvent()
and save it to a variable; this allows usa to destroy the observer so the code that would really ship the tweet is never run. You can effort out the alive app at https://hadley.shinyapps.io/ms-disengage.
runLater <- role ( activeness, seconds = iii ) { observeEvent ( invalidateLater ( seconds * m ), action, ignoreInit = TRUE, in one case = TRUE, ignoreNULL = FALSE, autoDestroy = FALSE ) } server <- role ( input, output, session ) { waiting <- NULL last_message <- Cipher observeEvent ( input $ tweet, { notification <- mucilage :: mucilage ( "Tweeted '{input$message}'" ) last_message <<- input $ message updateTextAreaInput ( session, "message", value = "" ) showNotification ( notification, action = actionButton ( "undo", "Undo?" ), duration = Cipher, closeButton = FALSE, id = "tweeted", type = "alert" ) waiting <<- runLater ( { true cat ( "Actually sending tweet...\northward" ) removeNotification ( "tweeted" ) } ) } ) observeEvent ( input $ undo, { waiting $ destroy ( ) showNotification ( "Tweet retracted", id = "tweeted" ) updateTextAreaInput ( session, "message", value = last_message ) } ) }
Trash
For actions that y'all might regret days later, a more sophisticated blueprint is to implement something similar the trash or recycling bin on your estimator. When you delete a file, it isn't permanently deleted but instead is moved to a holding cell, which requires a separate activeness to empty. This is like the "undo" option on steroids; yous take a lot of time to regret your activeness. It'due south also a bit like the confirmation; yous have to practice two separate deportment to make deletion permanent.
The primary downside of this technique is that it is substantially more complicated to implement (you have to accept a dissever "belongings cell" that stores the information needed to undo the action), and requires regular intervention from the user to avoid accumulating. For that reason, I think it's beyond the telescopic of all simply the well-nigh complicated Shiny apps, so I'm not going to testify an implementation hither.
Summary
This chapter has given you a number of tools to help communicate to the user what'southward happening with your app. In some sense, these techniques are mostly optional. But while your app volition piece of work without them, their thoughtful awarding tin can have a huge touch on the quality of the user experience. You can often omit feedback when you're the only user of an app, but the more people use information technology, the more that thoughtful notification volition pay off.
In the next chapter, y'all'll acquire how to transfer files to and from the user.
Source: https://mastering-shiny.org/action-feedback.html
0 Response to "Shiny App Ask User Browse a File and Show Error if Uploaded File Is Not Correct"
Post a Comment