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                  (                  )                  )                  }                              

Using `feedbackWarning()` to display alarm for invalid inputs. The app on the left shows a valid input, the app on the right shows an invalid (odd) input with warning bulletin. Come across alive at <https://hadley.shinyapps.io/ms-feedback>. Using `feedbackWarning()` to display alert for invalid inputs. The app on the left shows a valid input, the app on the right shows an invalid (odd) input with warning message. See live at <https://hadley.shinyapps.io/ms-feedback>.

Figure viii.one: Using feedbackWarning() to display warning for invalid inputs. The app on the left shows a valid input, the app on the right shows an invalid (odd) input with warning message. Meet live at https://hadley.shinyapps.io/ms-feedback.

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 valuesouthward 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 used value = "" 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,                  "!"                  )                  }                  )                  }                              

The app displays an uninformative error when it is loaded because a language hasn't been selected yet

Figure 8.2: The app displays an uninformative mistake when it is loaded because a language hasn't been selected still

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 of cancelOutput = TRUE: normally cancelling a reactive will reset all downstream outputs; using cancelOutput = TRUE leaves them displaying the last good value. This is important for textInput() 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                  (                  )                  )                  }                  )                  }                              

On load, the table is empty because the dataset name is empty. The data is shown after we blazon a valid dataset proper name (iris), and continues to be shown when printing backspace in order to type a new dataset proper noun. See live at <https://hadley.shinyapps.io/ms-require-cancel>. On load, the table is empty because the dataset name is empty. The data is shown later we blazon a valid dataset proper noun (iris), and continues to be shown when press backspace in order to type a new dataset proper noun. See live at <https://hadley.shinyapps.io/ms-require-cancel>. On load, the tabular array is empty because the dataset name is empty. The information is shown after we blazon a valid dataset proper name (iris), and continues to exist shown when press backspace in social club to type a new dataset name. See live at <https://hadley.shinyapps.io/ms-require-cancel>.

Figure 8.4: On load, the table is empty because the dataset name is empty. The data is shown after we blazon a valid dataset name (iris), and continues to be shown when press backspace in order to blazon a new dataset proper noun. Run into live at https://hadley.shinyapps.io/ms-require-cancel.

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                  )                  )                  }                  )                  }                              

If the inputs are valid, the output shows the transformation. If the combination of inputs is invalid, then the output is replaced with an informative message. If the inputs are valid, the output shows the transformation. If the combination of inputs is invalid, then the output is replaced with an informative message.

Figure 8.v: If the inputs are valid, the output shows the transformation. If the combination of inputs is invalid, so the output is replaced with an informative message.

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.

The progression of notifications later clicking 'Good night': the first notification appears, afterward 3 more seconds all notifications are shown, then the notifications start to fade away. See live at <https://hadley.shinyapps.io/ms-notify-persistent>. The progression of notifications afterwards clicking 'Expert nighttime': the first notification appears, subsequently three more than seconds all notifications are shown, then the notifications commencement to fade away. See live at <https://hadley.shinyapps.io/ms-notify-persistent>. The progression of notifications after clicking 'Expert night': the outset notification appears, after three more seconds all notifications are shown, then the notifications start to fade away. Meet alive at <https://hadley.shinyapps.io/ms-notify-persistent>.

Figure 8.6: The progression of notifications after clicking 'Skilful night': the first notification appears, after three more seconds all notifications are shown, then the notifications start to fade abroad. See live at https://hadley.shinyapps.io/ms-notify-persistent.

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 and closeButton = FALSE then that the notification stays visible until the task is consummate.

  • Store the id returned by showNotification(), and then pass this value to removeNotification(). The about reliable way to do and so is to employ on.exit(), which ensures that the notification is removed regardless of how the task completes (either successfully or with an error). You can learn more virtually on.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 Waiters 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:

A dialog box checking whether or not you want to delete some files.

Figure 8.x: A dialog box checking whether or not you want to delete some files.

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() and removeModal() to show and hibernate the dialog.

  • Nosotros observe events generated by the UI from modal_confirm. These objects aren't created statically in the ui, merely are instead dynamically added in the server() by showModal(). 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.

milleraftecte75.blogspot.com

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

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel