-
Notifications
You must be signed in to change notification settings - Fork 58
/
custom-templates-dependencies.Rmd
173 lines (139 loc) · 9.24 KB
/
custom-templates-dependencies.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
# Define dependencies {#custom-templates-dependencies}
## Introduction
The web provides a myriad of relevant **open-source** HTML templates like [Colorlib](https://colorlib.com) and [Creative Tim](https://www.creative-tim.com/bootstrap-themes/free). Many of the RinteRface packages are actually built on top of those resources. However, some of them may require more efforts to work with `{shiny}`, for reasons mentioned in Chapter \@ref(web-dependencies):
* `{shiny}` is built on top of [Bootstrap 3](https://getbootstrap.com/docs/3.3/) (HTML, CSS and Javascript framework), and changing the framework will not be a trivial endeavor. However, [shinymaterial](https://github.com/ericrayanderson/shinymaterial) and [shiny.semantic](https://github.com/Appsilon/shiny.semantic) are good examples that show this is possible.
* `{shiny}` relies on [jQuery](https://jquery.com). Consequently, all templates based upon [React](https://fr.reactjs.org), [Vue](https://vuejs.org) and other Javascript frameworks will not be natively supported. Again, there exist some [examples](https://github.com/alandipert/react-widget-demo/blob/3cd9087c62ece68e8bcdd86f69e4319ac994dc97/app.R) for React with `{shiny}` and more generally,
the [reactR](https://react-r.github.io/reactR/) package developed by Kent Russell and Alan Dipert. Chapter \@ref(going-further-reactR) provides a general overview.
In the next chapters, we will focus on the pretty [tabler.io](https://preview-dev.tabler.io/layout-dark.html) dashboard template (Figure \@ref(fig:tabler-dark)). We'll describe how to create an R wrapper on top of it, thereby making it available for all Shiny users.
```{r tabler-dark, echo=FALSE, fig.cap='Tabler dashboard overview.', out.width='75%', fig.align='center'}
knitr::include_graphics("images/practice/tabler-dark.png")
```
::: {.importantblock data-latex=""}
This chapter was written about two years ago, on top of the __1.0.0-alpha.7__ GitHub release (https://github.com/tabler/tabler/tree/14d0c001436b85d2a4533d63680d209affdf774b).
If the Tabler library significantly evolved since that date, the way to incorporate it into
the Shiny ecosystem remains __unchanged__. Hence, the methods we describe below may be __generalized__ to other
templates. We recommend reading this chapter before the next part in Chapter \@ref(workflow-charpente), during
which we present a more automated workflow, if you want to grasp the main concepts.
:::
## Discover the project
The first step of any template adaptation consists of __exploring__ the underlying GitHub [repository](https://github.com/tabler/tabler/tree/14d0c001436b85d2a4533d63680d209affdf774b)
and looking for mandatory elements, like CSS/JS __dependencies__.
This is a similar strategy if you want to incorporate an htmlwidget as well.
As depicted in Figure \@ref(fig:tabler-github), the most important folders are:
- __dist__, which contains CSS and JS files, as well as other libraries like Bootstrap and jQuery.
It is also a good moment to look at the version of each dependency that might conflict with Shiny.
- __demo__ is the website folder used for demonstration purpose.
This is our source to explore the template capabilities in depth.
The __scss__ and __build__ folder may be used to customize the tabler template directly. However, as stated above, directions on how to do so are out of the scope for this book.
```{r tabler-github, echo=FALSE, fig.cap='GitHub project exploration.', out.width='100%'}
knitr::include_graphics("images/practice/tabler-github.png")
```
## Identify mandatory dependencies
`Bootstrap 4`, `jQuery`, `tabler.min.css` and `tabler.min.js` are key elements for the template,
contrary to flag icons, which are optional (and take a lot of space).
If your goal is to release your template on CRAN, be mindful of the 5 Mb maximum size limit.
From personal experience, I can attest that this is quite challenging to manage.
To inspect dependencies, we proceed as follows:
- __Download__ or __clone__ the GitHub repository.
- Go to the __demo folder__ and open the `layout-dark.html` file.
- Open the __HTML inspector__.
As shown in Figure \@ref(fig:tabler-deps) left-hand side, we need to include the `tabler.min.css` from the header. If you are not convinced, try to remove it from the DOM and see what happens. [jqvmap](https://www.10bestdesign.com/jqvmap/) is actually related to an external visualization plugin used in the demo. Finally, the `demo.min.css` file is for the demo purpose.
This will not prevent the template from working, so we will skip it for now. So far so good, we only need one file thus.
```{r tabler-deps, echo=FALSE, fig.show = "hold", out.width = "50%", fig.align = "default", fig.cap="Tabler dependencies overview."}
knitr::include_graphics("images/practice/tabler-deps-1.png")
knitr::include_graphics("images/practice/tabler-deps-2.png")
```
JavaScript dependencies are shown on the right-hand side and located at the end of the body tag. Because we will not need all chart-related dependencies, such as `apexcharts`, `jquery.vmap` and `vmap world`, we may safely ignore them. We only retain the Bootstrap 4, jQuery core and `tabler.min.js`, in the same order.
## Bundle dependencies
With the help of the `htmlDependency()` function, we are going to create our main Tabler HTML dependency containing all assets to allow our template to render properly. In this example, we are going to cheat a bit: instead of handling local files, we will use a CDN (content delivery network) that hosts all necessary Tabler [assets](https://www.jsdelivr.com/package/npm/tabler). This avoids having to include all the necessary files in the R package, as well as in a GitHub repository.
::: {.warningblock data-latex=""}
For a __production__ template that is designed to go on CRAN,
we recommend hosting files __locally__, as described in Chapter \@ref(workflow-charpente).
:::
```{r}
library(htmltools)
tabler_cdn <- "https://cdn.jsdelivr.net/npm/[email protected]/"
tablers_deps <- htmlDependency(
name = "tabler",
version = "1.0.7", # we take that of tabler,
src = c(href = tabler_cdn),
script = "dist/js/tabler.min.js",
stylesheet = "dist/css/tabler.min.css"
)
```
We advise the reader to create one HTML dependency per element. The Bootstrap version is `4.3.1`. We can also use a CDN:
```{r}
bs4_cdn <- "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/"
bs4_deps <- htmlDependency(
name = "Bootstrap",
version = "4.3.1",
src = c(href = bs4_cdn),
script = "js/bootstrap.bundle.min.js"
)
```
We finally create our dependency manager:
```{r}
# add all dependencies to a tag. Don't forget to set
# append to TRUE to preserve any existing dependency
add_tabler_deps <- function(tag) {
# below, the order is of critical importance!
deps <- list(bs4_deps, tablers_deps)
attachDependencies(tag, deps, append = TRUE)
}
```
Notice the dependencies order in the `deps` list. This will be exactly the same order in the `head` of the HTML page. Some libraries require being loaded at a specific place, like the Tabler dependencies, which must come after Bootstrap.
Let's see how to use `add_tabler_deps()`. We consider a `<div>` placeholder and check for its dependencies with `findDependencies()`.
Then, we wrap it with `add_tabler_deps()`:
```{r}
tag <- div()
findDependencies(tag)
```
```{r, eval=FALSE}
tag <- add_tabler_deps(div())
findDependencies(tag)
```
```{r, echo=FALSE, results='asis'}
tmp_code <- '#> [[1]]
#> List of 10
#> $ name : chr "Bootstrap"
#> $ version : chr "4.3.1"
#> $ src :List of 1
#> ..$ href: chr "https://.../bootstrap/4.3.1/js/"
#> $ meta : NULL
#> $ script : chr "bootstrap.bundle.min.js"
#> $ stylesheet: NULL
#> $ head : NULL
#> $ attachment: NULL
#> $ package : NULL
#> $ all_files : logi TRUE
#> - attr(*, "class")= chr "html_dependency"
#>
#> [[2]]
#> List of 10
#> $ name : chr "tabler"
#> $ version : chr "1.0.7"
#> $ src :List of 1
#> ..$ href: chr "https://.../[email protected]/dist/"
#> $ meta : NULL
#> $ script : chr "js/tabler.min.js"
#> $ stylesheet: chr "css/tabler.min.css"
#> $ head : NULL
#> $ attachment: NULL
#> $ package : NULL
#> $ all_files : logi TRUE
#> - attr(*, "class")= chr "html_dependency"'
code_chunk_custom(tmp_code)
```
As shown above, our dependencies are applied to the div, in the correct order. This order is set by the list `list(bs4_deps, tablers_deps)` and allows us to avoid potential __conflicts__. If we try to run this simple tag in a Shiny app, we notice that all dependencies are added to the `<head>` tag, whereas the original template loads JavaScript dependencies in the `<body>`.
::: {.noteblock data-latex=""}
Unfortunately, `{htmltools}` does not allow developers to distribute dependencies in different places yet.
:::
Here there is no impact, but this might be no-go for templates requiring JavaScript to be placed in the body. In practice, this is challenging to guess and may only be solved by manual testing.
```{r, eval=FALSE}
library(shiny)
ui <- fluidPage(tag)
server <- function(input, output, session) {}
shinyApp(ui, server)
```
Even though the `add_tabler_deps()` function may be applied to any tag, we will use it with the core HTML template, which remains to be designed.
Would you like to see if our dependency system works? Let's meet in the next chapter to design the main dashboard layout.