Skip to content

Commit

Permalink
Writing about file transfers
Browse files Browse the repository at this point in the history
  • Loading branch information
hadley committed Sep 16, 2019
1 parent 35a47c0 commit 636835f
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 25 deletions.
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,9 @@ Built with [bookdown](https://bookdown.org/yihui/bookdown/).
1. Shiny in action
1. Workflow / organising your app.
1. User feedback
* `update` functions
* [Progress bars](https://shiny.rstudio.com/articles/progress.html)
* [Validation](https://shiny.rstudio.com/articles/validation.html)
* [Notifications](https://shiny.rstudio.com/articles/notifications.html)
1. Modules
1. Uploading/downloading data
1. Modules
1. Programming the tidyverse
1. Generating static reports from Shiny

1. Mastering UI
1. Tables
Expand Down
133 changes: 116 additions & 17 deletions action-transfer.Rmd
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
# Uploads and downloads {#action-transfer}

<!-- TODO: update link/contents in basic-UI -->

```{r, include = FALSE}
source("common.R")
```

Transferring files to and from the user is a common feature of apps. This is most commonly used to upload or download data, or to download a report generated from features of the app. This chapter shows you how to combine UI and server components make it easy for your users to get data to and from your app.

```{r setup}
library(shiny)
```

## Upload

Upload a file is straightforward on the UI side: you just need to call `fileInput()`. It's a little more work on the server side because `input$file` returns a data frame with four columns:
Upload a file is straightforward on the UI side: you just need to call `fileInput(id, label)`. It also has `multiple` and `accept` arguments that I'll discuss below. You can customise the appearance with `width`, `buttonLabel` and `placeholder` arguments, which you can learn more about in `?fileInput`.

Most of the complication arises on the server side because `input$file` returns a data frame with four columns:

* `name`: name of the file.

Expand Down Expand Up @@ -60,7 +66,7 @@ server <- function(input, output, session) {
ext <- tools::file_ext(input$file$filename)
validate(need(ext == "csv", "Please upload a CSV file"))
read.csv(input$file$datapath)
vroom::vroom(input$file$datapath, delim = ",")
})
output$description <- renderTable({
Expand All @@ -75,19 +81,18 @@ Note that if `multiple = FALSE` (the default), then `input$file` will be a singl

Downloading a file is a little more complicated as you need a specific component in the server function that works a little differently to the other render functions.

* In UI, use `downloadButton()` or `downloadLink()` to give the user
something to initiate to download a file.
* In UI, use `downloadButton(id)` or `downloadLink(id)` to give the user
something to initiate to download a file. (There are other arguments, but
you'll hardly ever need them; see `?downloadButton` for the details.)

* In server, use `downloadHandler()` to respond to the user and send them
a file.
* In server, use `output$id <- downloadHandler()` to respond to the user and
send them a file.

`downloadHandler()` is a little different to other outputs because it requires two functions: one that determines the file name of the output, and the other that creates a file at a specified path:
`downloadHandler()` is a little different to other outputs because it requires two functions: one that determines the file name of the output, and the other that creates a file at a specified path. The following app shows off the basics by creating an app that lets you download any dataset in the datasets package as a csv file.

```{r}
datasets <- ls("package:datasets")
ui <- fluidPage(
selectInput("dataset", "Pick a dataset", datasets),
selectInput("dataset", "Pick a dataset", ls("package:datasets")),
downloadButton("download")
)
server <- function(input, output, session) {
Expand All @@ -106,15 +111,110 @@ server <- function(input, output, session) {
}
```

Note the two important arguments to `downloadHandler()`:

* `filename` should be a function with no arguments that returns a single
string giving the name of the file. This is the string that will appear in
the download dialog box. I recommend avoiding special characters.

If the file name is fixed, you can skip the function and just set it to
a string.

* `content` should be a function with a single argument that tells you where
to save the file. The return argument of this function is ignored; it's
called purely for its side-effect of creating a file at the given path.

### Parametermised RMarkdown reports

<!-- https://shiny.rstudio.com/articles/generating-reports.html -->
Another common use of file downloads is to prepare a report that summarises the result of interactive exploration in the Shiny app. One powerful way to generate such a report is with a parameterised RMarkdown document, <https://bookdown.org/yihui/rmarkdown/parameterized-reports.html>. A parameterised RMarkdown file has special YAML metadata:

Copy to temporary directory/file. (You'll also need to copy any other resource that your app uses.)
```yaml
title: My Document
output: html_document
params:
year: 2018
region: Europe
printcode: TRUE
data: file.csv
```
And inside the document you can refer to those values with `params$year`, `params$region` etc. What makes this technique powerful is you can also set the parameters using code, by calling `rmarkdown::render()` with the `params` argument. This makes it possible to generate a range of RMarkdown reports from a single app.

How to run in another process.
Here's a simple example taken from <https://shiny.rstudio.com/articles/generating-reports.html>, which describes this technique in more detail.

Other formats
```{r}
ui <- fluidPage(
sliderInput("n", "Number of points", 1, 100, 50),
downloadButton("report", "Generate report")
)
server <- function(input, output, session) {
output$report <- downloadHandler(
filename = "report.html",
content = function(file) {
params <- list(n = input$n)
rmarkdown::render("report.Rmd",
output_file = file,
params = params,
envir = new.env(parent = globalenv())
)
}
)
}
```

If you want to produce other output formats, just change the output format in the `.Rmd`, and make sure to update the extension.

There are a few other tricks worth knowing about:

* If the report takes some time to generate, use one of the techniques from
Chapter \@ref(action-feedback) to let the user know that your app is
working.

* Often, when deploying the app, you can't write to the working directory,
which RMarkdown will attempt to do. You can work around this by copying
the report to a temporary directory:

```{r}
report_path <- tempfile(fileext = ".Rmd")
file.copy("report.Rmd", report_path, overwrite = TRUE)
```

Then replaaing `"report.Rmd"` in the call to `rmarkdown::render()` with
`report_path`.

* By default, RMarkdown will render the report in the current process, which
means that it will inherit many settings from the Shiny app (like loaded
packages, options, etc). For greater robustness, I recommend running in
fresh R session by using the callr package.

```{r, eval = FALSE}
render_report <- function(input, output, params) {
rmarkdown::render(input,
output_file = output,
params = params,
envir = new.env(parent = globalenv())
)
}
server <- function(input, output) {
output$report <- downloadHandler(
filename = "report.html",
content = function(file) {
params <- list(n = input$slider)
callr::r(
render_report,
list(input = report_path, output = file, params = params)
)
}
)
}
```

You can see all these pieces put together in `rmarkdown-report/`.

Later, in Chapter XYZ, we'll come back to generating a complete report of all the code that your app has executed.

## Case study

Expand Down Expand Up @@ -167,4 +267,3 @@ server <- function(input, output, session) {
}
```


8 changes: 6 additions & 2 deletions introduction.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ source("common.R")

## What is Shiny?

## What you will learn
## What will you learn?

If you've never used Shiny before, welcome!

Expand All @@ -22,7 +22,11 @@ The biggest drawback of reactive programming is that it's a fundamentally new pr

Finally, in "Taming Shiny" we'll finish up a survey of useful techniques for making your Shiny apps work well in production. You'll learn how to measure and improve performance, debug problems when they go wrong, and manage your app's dependencies.

## What you won't learn
## How to keep learning



## What won't you learn?

The focus of this book is making effective Shiny apps and understanding the underlying theory of reactivity. I'll do my best to show off best practices for data science, R programming, and software engineering at the same time as well, but you'll need other references to master these other important components.

Expand Down
34 changes: 34 additions & 0 deletions rmarkdown-report/app.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
library(shiny)

# Copy report to temporary directory. This is mostly important when
# deploying the app, since often the working directory won't be writable
report_path <- tempfile(fileext = ".Rmd")
file.copy("report.Rmd", report_path, overwrite = TRUE)

render_report <- function(input, output, params) {
rmarkdown::render(input,
output_file = output,
params = params,
envir = new.env(parent = globalenv())
)
}

ui <- fluidPage(
sliderInput("n", "Number of points", 1, 100, 50),
downloadButton("report", "Generate report")
)

server <- function(input, output) {
output$report <- downloadHandler(
filename = "report.html",
content = function(file) {
params <- list(n = input$n)
callr::r(
render_report,
list(input = report_path, output = file, params = params)
)
}
)
}

shinyApp(ui, server)
12 changes: 12 additions & 0 deletions rmarkdown-report/report.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: "Dynamic report"
output: html_document
params:
n: NA
---

A plot of `r params$n` random points.

```{r}
plot(rnorm(params$n), rnorm(params$n))
```

0 comments on commit 636835f

Please sign in to comment.