/
08_structure.Rmd
171 lines (119 loc) · 7.55 KB
/
08_structure.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# Structuring a complex app {#structure}
So far, we've been mostly structuring our app entirely in the `r path("app.R")` file, apart from some of the web helper files for `r glossary("CSS")` and `r glossary("JavaScript")`. However, once your apps start getting relatively complex, you might find it easier to move some of the code into external `r path(".R")` files and using `source()` to include them. There are a few things to watch out for when you do this.
## External Server Functions
You can define `r glossary("function", "functions")` you want to use in your app at the top of the `r path("app.R")` file, but that can make that file difficult to parse pretty quickly. The basic template includes external functions with the line:
```{r}
source("scripts/func.R") # helper functions
```
This file contains definitions for the functions `r func("debug_msg")` and `r func("debug_sprintf")`. You can add your own custom functions to this file or to another file that you source in separately.
::: {.warning data-latex=""}
All `r path(".R")` files inside a directory called `r path("R")` will run before the app starts up, even if you don't source them into the app. You can use this to set up any functions your app needs without having to use `r func("source")`, but I prefer to explicitly include external files, so I keep external functions in a directory called `scripts`.
:::
### Sourcing Locally
It can be tricky to use shiny functions to external files. For example, you can't just move the contents of `r func("server")` to an external file called `r path("scripts/logo.R")` and source the file in like this:
```{r}
server <- function(input, output, session) {
source("scripts/logo.R")
}
```
You'll get an error like: "Error in output$logo <- renderImage({ : object 'output' not found". This is because the input and output objects only work like you'd expect when they are inside `r func("server")`
However, you can source in external code inside `r func("server")` by setting the `local` `r glossary("argument")` to TRUE.
```{r}
server <- function(input, output, session) {
source("scripts/logo.R", local = TRUE)
}
```
You might find it useful to break up parts of the server logic for a very big app into separate files like this, but it's more common to keep any code that uses reactive functions inside `r func("server")` in the `r path("app.R")` file, and move large sections of code inside those functions to externally defined functions.
For example, you could define the function `r func("logo_image")` in the external file `r path("scripts/logo.R")` like this:
```{r}
logo_image() <- function() {
list(src = "www/img/shinyintro.png",
width = "300px",
height = "300px",
alt = "ShinyIntro hex logo")
}
```
The following in the `r path("app.R")` file keeps the reactive function `r func("renderImage")` inside `r func("server")`, but lets you reduce the number of lines of code.
```{r}
source("scripts/logo.R")
server <- function(input, output, session) {
source("scripts/logo.R", local = TRUE)
output$logo <- renderImage(logo_image(), deleteFile = FALSE)
}
```
### Inputs and Outputs
The objects `input` and `output` aren't available by default to externally defined functions. Let's add an action button to the ui for our app, `actionButton("change", "Change Image")`, and change the `r func("logo_image")` function so that it returns the ShinyIntro logo on odd-numbered clicks of the change button, and the psyTeachR logo on even-numbered clicks.
```{r}
logo_image <- function() {
odd_clicks <- input$change%%2 == 1
src <- ifelse(odd_clicks,
"www/img/shinyintro.png",
"www/img/psyteachr.png")
list(src = src,
width = "300px",
height = "300px",
alt = "ShinyIntro hex logo")
}
```
If you try to run this, you'll get an error message like, "Error in logo_image: object 'input' not found". This is because the external function doesn't have access to reactive objects like `input`, `output`, `session`, or any `r func("reactiveValues")`.
The best solution is to pass any variables to the function that you need. In some circumstances, you can pass the whole `input` object, but that's seldom necessary.
Here, we change `r func("logo_image")` to take a single argument called `r arg("change")` and replace `input$change` with this argument.
```{r}
logo_image <- function(change) {
odd_clicks <- change%%2 == 1
src <- ifelse(odd_clicks,
"www/img/shinyintro.png",
"www/img/psyteachr.png")
list(src = src,
width = "300px",
height = "300px",
alt = "ShinyIntro hex logo")
}
```
Then we just have to pass the value of `input$change` to `r func("logo_image")` inside `r func("renderImage")`, where the `input` object is available.
```{r}
server <- function(input, output, session) {
output$logo <- renderImage({
logo_image(input$change)
}, deleteFile = FALSE)
}
```
Don't worry too much if this isn't making a lot of sense yet. The main thing I want you to take away from this section is that when you try to move some server code to external files, you might get errors (I frequently do). I hope that will remind you of this lesson and you'll have a better idea about where to start looking for the solution.
## External UI Files
Defining a complex UI can be very challenging. The basic template uses a pattern that I find helpful with apps that have multiple tab items. I assign each tab to an object and then include the tabs of the app in `r func("dashboardBody")` like this:
```{r}
tabItems(
intro_tab,
questionnaire_tab,
feedback_tab,
info_tab
)
```
For a simple app, you can define the tabs in `r path("app.R")` just before you define the `ui`. You can do the same for any components of the `ui`, such as `r func("dashboardHeader")` or `r func("dashboardSidebar")`. When the sections start getting complex, you can move them into external files and source them in.
### UI Lists
![](images/structure_ui.png){style="float: right; width: 100%; max-width: 420px;"}
When parts of the UI repeat or can be created programmatically instead of manually, you can use `r func("apply")` or `r func("map")` functions to create a list of UI components. It can be a little tricky to figure out how to add a list of components into the UI, but this can be accomplished with `r func("do.call")`.
Here's an example of how you would programatically create select inputs for each categorical column of the `starwars` dataset from `r pkg("dplyr")` and add them to a `r func("box")`.
```{r}
# get the categorical columns
col_is_char <- sapply(starwars, is.character)
categorical_cols <- names(starwars)[col_is_char]
# set up the selectInputs
select_inputs <- lapply(categorical_cols, function(col) {
unique_vals <- unique(starwars[[col]])
selectInput(inputId = col, label = col, choices = unique_vals)
})
# add container arguments to select_inputs
select_inputs$title = "Select the Categories"
select_inputs$solidHeader = TRUE
select_inputs$width = 4
# add to container
select_box <- do.call(box, select_inputs)
```
## Exercises {#exercises-structure}
### UI {-}
Clone the reactive_demo, move the boxes in the ui to an external file, and source them in.
### Server {-}
Make a custom function in `r path("scripts/func.R")` that creates the plot. Use that in `r func("server")`.
## Your App {#your-app-structure}
In the app you're developing, see if there are any long functions inside reactive functions in `r func("server")` that can be moved to `r path("scripts/func.R")` or another external file. Move each tab into an external file and source it into `r path("app.R")`.