generated from r4ds/bookclub-template
-
Notifications
You must be signed in to change notification settings - Fork 11
/
04_handle-html-dependencies-with-htmltools.Rmd
321 lines (235 loc) · 7.47 KB
/
04_handle-html-dependencies-with-htmltools.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# Handle HTML dependencies with htmltools
**Learning objectives:**
* How we can utilize {htmltools} manage web dependencies (CSS and JS).
* Familiarize ourselves on how to use the `htmlDependency()` & `tagList()` to properly handle web dependencies in Shiny apps
* Learn about the `findDependencies()`, `suppressDependencies()` and `resolveDependencies()` functions and understand when and why they are necessary in Shiny app development.
<br>
> This opens the doors to work with almost **any web framework**
## Finding a card {-}
We saw this card on __Material Design for Bootstrap (MDB)__ [documentation](https://mdbootstrap.com/docs/standard/components/cards/).
![](image/04-handle-html-dependencies/01-mdb-card.png)
```html
<div class="card">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<button type="button" class="btn btn-primary" data-mdb-ripple-init>Button</button>
</div>
</div>
```
## Wrapping html into a function {-}
```html
<div class="card">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<button type="button" class="btn btn-primary" data-mdb-ripple-init>Button</button>
</div>
</div>
```
```{r}
library(shiny)
my_card <- function(...) {
withTags(
div(
class = "card",
div(
class = "card-body",
h5(class = "card-title", "Card title"),
p(class = "card-text", "Card content"),
button(
type = "button",
class = "btn btn-primary",
"Button"
)
)
)
)
}
```
## Checking the result {-}
![](image/04-handle-html-dependencies/02-card-without-dependencies.png)
![](image/04-handle-html-dependencies/03-using-bootstrap-3-for-card.png)
## Finding dependencies {-}
We need to import the _Material Design for Bootstrap_ dependency, to make that we have 2 alternatives:
- Defining the needed styles in local file in the `www/`.
- Use a content delivery network (CDN) like [cdnjs](https://cdnjs.com/), where we can find [mdb-ui-kit](https://cdnjs.com/libraries/mdb-ui-kit).
```r
mdb_cdn <- "https://cdnjs.cloudflare.com/ajax/libs/"
mdb_css <- paste0(mdb_cdn, "mdb-ui-kit/3.6.0/mdb.min.css")
```
## Adding a link on the head tag {-}
![](image/04-handle-html-dependencies/06-mdb-card-deps.png)
```r
fluidPage(
tags$head(
tags$link(
rel = "stylesheet",
type = "text/css",
href = mdb_css
)
)
)
```
![](image/04-handle-html-dependencies/05-using-link-tag.png)
## `includeCSS()` on the head tag {-}
![](image/04-handle-html-dependencies/06-mdb-card-deps.png)
```r
fluidPage(
tags$head(
includeCSS(path = mdb_css)
)
)
```
![](image/04-handle-html-dependencies/04-include-css-function.png)
## Using `htmlDependency()` {-}
It has the advantage of adding the dependency in the header with its __name__ and __version__.
![](image/04-handle-html-dependencies/07-missing-dependency-name.png)
## Using `htmlDependency()` {-}
Main parameters:
- A __name__.
- A __version__ (useful to remember on which version it is built upon).
- A __path__ to the dependency (can be a CDN or a local folder).
- __script__ and __stylesheet__ to respectively pass css and scripts.
```r
# handle dependency
mdb_cdn <- "https://cdnjs.cloudflare.com/ajax/libs/"
mdb_card_dep <- function() {
htmlDependency(
name = "mdb-card",
version = "1.0",
src = c(href = mdb_cdn),
stylesheet = "mdb-ui-kit/3.6.0/mdb.min.css"
)
}
```
> It is __crucial to wrap the created dependency in a function__ since the path has to be determined at __run time__ and not when the package builds.
## Using `htmlDependency()` {-}
Then we just need to the created element and the dependency into `tagList()` function.
```r
my_card_with_deps <- function(...) {
cardTag <- my_card(...)
tagList(cardTag, mdb_card_dep())
}
```
> __Now we can share our element in a package__.
## `htmlDependency()` disanvantage {-}
The dependency was added but in the **wrong place** (before bootstrap).
![](image/04-handle-html-dependencies/08-dependency-name-added.png)
## Extracting web dependencies {-}
__box tag__ won't work since __shiny__ does not have __shinydashboard__ dependencies
`htmltools::findDependencies()` that looks for all dependencies attached to a tag.
```{r, message=FALSE}
library(shinydashboard)
dashboard_ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody()
)
library(htmltools)
dashboard_deps <- findDependencies(dashboard_ui)
sapply(dashboard_deps, \(x) paste0(x$name, ": ", x$version))
```
## font-awesome {-}
It handles icons.
```{r message=TRUE}
print(dashboard_deps[[2]])
```
## Using extracted web dependencies {-}
Now we can add the dependencies with the element to we want to use but using the `fluidPage()` function.
```r
my_dashboard_box <- function(title, status) {
tagList(
box(title = title, status = status),
dashboard_deps
)
}
library(shiny)
library(shinydashboard)
ui <- fluidPage(
tags$style("body { background-color: gainsboro; }"),
titlePanel("Shiny with a box"),
my_dashboard_box(title = "My box", status = "danger"),
)
server <- function(input, output) {}
shinyApp(ui, server)
```
![](image/04-handle-html-dependencies/09-shiny-import-box.png)
## Suppress Dependencies {-}
If we have a conflict we can use `suppressDependencies()` as an element of the __body tag__.
![](image/04-handle-html-dependencies/10-bootstrap-dependency.png)
## Removing AdminLTE {-}
```r
library(shiny)
library(shinydashboard)
shinyApp(
ui = dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(suppressDependencies("AdminLTE")),
title = "Dashboard example"
),
server = function(input, output) { }
)
```
## Removing redundant dependencies {-}
If want to update the **last version** of Font Awesome icons (from 5.15.1 to 6.4.2)
```{r}
jsdelivr_cdn <- "https://cdn.jsdelivr.net/npm/@fortawesome/"
ft_aws <- paste0(jsdelivr_cdn, "[email protected]/")
new_icon_dep <- htmlDependency(
name = "font-awesome",
version = "5.15.1",
src = c(href = ft_aws),
stylesheet = "css/all.min.css"
)
icon_deps <- list(
new_icon_dep,
findDependencies(shiny::icon("th"))[[1]]
)
resolveDependencies(icon_deps)
```
## Insert custom script in the head {-}
To embed dynamic user scripts in a dependency.
```{r}
options <- list(
sidebarExpandOnHover = TRUE,
boxWidgetSelectors = list(
remove = '[data-widget="remove"]'
)
)
config_script <- function(options) {
htmlDependency(
"options",
as.character(utils::packageVersion("shinydashboardPlus")),
src = c(file = system.file(
"shinydashboardPlus-2.0.0",
package = "shinydashboardPlus")
),
head = if (!is.null(options)) {
paste0(
"<script>var AdminLTEOptions = ",
jsonlite::toJSON(
options,
auto_unbox = TRUE,
pretty = TRUE
),
";</script>"
)
}
)
}
# show the script
print(HTML(config_script(options)$head))
```
## Meeting Videos
### Cohort 1
`r knitr::include_url("https://www.youtube.com/embed/BdQMvsIN6Pg")`
<details>
<summary> Meeting chat log </summary>
```
00:02:21 Russ: Hi everyone
00:17:23 Russ: TODO: Why is tags$head for a local stylesheet potentially bad when working with other developers
00:24:57 Russ: TODO: What is the benefit of htmlDependency for an external html dependency
```
</details>