forked from DivadNojnarg/outstanding-shiny-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
/
web-intro.Rmd
383 lines (278 loc) · 20.6 KB
/
web-intro.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
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# Shiny and the Web {#web-intro}
`{shiny}` [@R-shiny] allows the development of __web applications__ with R in minutes. Let's face it: this is quite mind blowing. While this may not be a production-ready app, it will still be a fully functional, working prototype. Believe me, doing a web application with pure __HTML__, __CSS__ and __JavaScript__ is more difficult, especially for someone with a non-web developer background.
We first load `{shiny}`:
```{r}
library(shiny)
```
## Shiny generates HTML code from R
I propose to warm up with a little exercise:
1. Copy and paste this code to the R console and click enter.
```{r, eval=FALSE}
h1("Hello World")
```
2. What do you observe?
The output is HTML code. For an R developer, being able to generate HTML code from R allows the developer to remain focused on the main task instead of the web development burdens.
Most of the time, a production Shiny app requires custom elements that are not contained or hidden in Shiny's core.
Is a Shiny app less customizable than a classic web app? Not at all! Under the hood, Shiny has its own engine to build HTML tags, through R, meaning that all HTML elements are available. You may also include any custom [JavaScript](https://shiny.rstudio.com/articles/packaging-javascript.html) code and styles with CSS. In Chapter \@ref(htmltools-overview), we will shed the light on the underlying mechanisms that allow you to create HTML from R code.
Huumm ... I feel you don't believe me ... OK, fine ... let me show you something!
## Be a DJ
What you see below in Figure \@ref(fig:dj-app) is a Shiny app. Yes, I swear!
```{r dj-app, echo=FALSE, fig.cap='Shiny app with the Pioneer CDJ 2000 NXS2 professional gear look.', out.width='40%', fig.align='center'}
knitr::include_graphics("images/intro/dj-app.png")
```
If you are still not convinced, have a look at the below demonstration.
```{r, echo=FALSE, results='asis'}
code_chunk(OSUICode::get_example("intro/dj-system", view_code = FALSE), "r")
```
Wait until the blue waveform appears on the player's screen. Then click on the green play button to stop and start the music (the space key is also supported). Besides, the waveform is interactive, thereby allowing you to browse through the current track. The rotating part (central part of the jog wheel) indicates the read position and other useful information.
What is this magic? Under the hood, this Shiny [app](https://github.com/DivadNojnarg/OSUICode/tree/266ad20f450fdc6a8c939216287b6d67bc9e828d/inst/intro/dj-system) only consists of:
- 111 lines of CSS.
- 29 lines of JavaScript code.
- 2 png images (dj gear + rotating wheel).
- 36 lines of R code, including the `{wavesurfer}` htmlWidget [package](https://github.com/Athospd/wavesurfer/tree/751705010865e263c2cedc9bea6630c1a5d47f09) to display the waveform.
- Few custom HTML tags.
- And is inspired by this [article](https://codepen.io/ruise/pen/MVPgrQ).
We must acknowledge it is still far from offering the same features as the original professional DJ [gear](https://www.pioneerdj.com/en-gb/product/player/cdj-2000nxs2/black/overview/), but it is a fairly good start!
As you noticed, you will have to acquire a bit of HTML, CSS and JS knowledge to reach the same level of result. Guess what? This is exactly the purpose of this book!
Are you ready to become a Shiny wizard?
## HTML 101 {#web-intro-html}
This chapter provides a short introduction to the three main web languages, namely HTML, CSS and JavaScript. The following content is crucial to understand Chapter \@ref(htmltools-overview) about HTML generation from R.
### HTML basics
HTML stands for (Hypertext Markup Language). An HTML file contains __tags__ that may be divided into two types:
- __Paired__ tags, where the text is inserted between the opening and the closing tag.
- __Self-closing__ tags.
```{r, echo=FALSE, results='asis'}
html_code <- "<!-- paired-tags -->
<p></p>
<div></div>
<!-- self-closing tags -->
<iframe/>
<img/>
<input/>
<br/>"
code_chunk_custom(html_code, "html")
```
Tags may be divided into three categories, based on their role:
- __Structure__ tags: they constitute the skeleton of the HTML page (`<title></title>`, `<head></head>`, `<body></body>`).
- __Control__ tags: script, inputs and buttons (and more). Their role is to include external resources, provide interactivity with the user.
- __Formatting__ tags: to control some of the wrapped text properties like its size and font.
Inside an HTML document, tag elements obey the __box__ model, which briefly defines the element internal margins (padding), margins (space between multiple elements), the width and height. Elements are displayed according to the __flow layout__ model (Figure \@ref(fig:normal-flow-layout)). We distinguish block and inline elements:
- __Block__ elements may contain other tags and take the full width (block or inline). `<div></div>` is the most commonly used block element. All elements of a block are printed on top of each other.
- __Inline__ elements (`<span></span>`, `<a></a>`) are printed on the same line. They cannot contain block tags; for instance `<span><div><p>Hello World</p></div></span>` is not valid, but may contain other nested inline tags like `<a><img/></a>` (creates a clickable image pointing to a specific location).
- __Inline-block__ elements allow one to insert a block element in an inline.
```{r normal-flow-layout, echo=FALSE, fig.cap='Flow layout and box model in an HTML document.', out.width='100%'}
knitr::include_graphics("images/intro/normal-flow-layout.png")
```
Importantly, `<div>` and `<span>` are generic tags and don't have any __semantic__ meaning, contrary to `<header>` and `<footer>`, which allow developers to structure the HTML page, as depicted by Figure \@ref(fig:html-semantic-tags). If you happen to insert a structural tag like `<aside></aside>` in a basic HTML document, it will not automatically create a sidebar. Instead, it helps to maintain a readable and meaningful code. If you wish to give a proper structure to the page, let's meet below in section \@ref(html-and-css) and later in Chapter \@ref(beautify-css). `<div>` and `<span>` are used whenever no semantic block and inline container may be applied, respectively.
```{r html-semantic-tags, echo=FALSE, fig.cap='Example of semantic tags in the {bs4Dash} Shiny Bootstrap 4 dashboard template.', out.width='100%'}
knitr::include_graphics("images/intro/html-semantic-tags.png")
```
### Tag attributes
__Attributes__ are text elements allowing developers to specify some tag properties. For instance for a link tag (`<a></a>`), we actually expect more than just the tag itself, such as a target url and how to open the new page. In all previous examples, tags don't have any attributes. Yet, there exists a large range of attributes, and we will only see two of them for now (the reason is that these are the most commonly used in CSS and JavaScript):
- __class__ that may be shared between multiple tags.
- __id__ that must be __unique__.
```{r, echo=FALSE, results='asis'}
html_code <- '<div class="awesome-item" id="myitem"></div>
<!-- the class awesome-item may be applied to multiple tags -->
<span class="awesome-item"></span>'
code_chunk_custom(html_code, "html")
```
Both attributes are widely used by CSS and JavaScript to apply a custom style to a web page (see Chapter \@ref(survival-kit-javascript)). Class attributes apply to multiple elements, however the id attribute is restricted to only one item.
Interestingly, there is another attribute category, know as __non-standard attributes__ like `data-toggle`. We see them later in the book in Chapter \@ref(custom-templates-skeleton).
### The simplest HTML skeleton {#simplest-html-template}
An HTML page is a collection of tags which are __interpreted__ by the web browser step by step. The simplest HTML skeleton may be defined as follows:
```{r, echo=FALSE, results='asis'}
html_code <- '<!DOCTYPE HTML>
<html lang="en">
<head>
<!-- head content here -->
<title>A title</title>
</head>
<body>
<!-- body content here -->
</body>
</html>'
code_chunk_custom(html_code, "html")
```
- `<html>` is the main wrapper.
- `<head>` and `<body>` are the two main children.
* `<head>` contains dependencies like styles and JavaScript files (but not only).
* `<body>` contains the page content and it is displayed on the screen. JavaScript files are often added just before the end of the `<body>`.
::: {.warningblock data-latex=""}
Only the **body** content is **displayed** on the screen. W3C validation (https://validator.w3.org/#validate_by_input) imposes at least a `title` tag in the `head` section and a `lang` attribute to the `html` tag.
:::
Let's write the famous `Hello World` in HTML:
```{r, echo=FALSE, results='asis'}
html_code <- '<!DOCTYPE HTML>
<html lang="en">
<head>
<!-- head content here -->
<title>A title</title>
</head>
<body>
<p>Hello World</p>
</body>
</html>'
code_chunk_custom(html_code, "html")
```
In order to preview this page in a web browser, you have to save the above snippet to a script `hello-world.html` and double-click on it. It will open with your default web browser.
Below is how we would do it with a Shiny app:
```{r, eval=FALSE}
ui <- fluidPage(p("Hello World"))
server <- function(input, output, session) {}
shinyApp(ui, server)
```
From outside, it looks identical! Are you sure about this? Let's meet in Chapter \@ref(web-dependencies) to have a deeper look.
### About the Document Object Model (DOM)
The __DOM__ stands for "Document Object Model" and is a convenient representation of the HTML document. If we consider the last `Hello World` example, the associated DOM __tree__ may be inspected in Figure \@ref(fig:html-dom).
#### Visualizing the DOM with the developer tools
The developer tools are a crucial way to work with websites, and particularly customize Shiny apps. As shown in Figure \@ref(fig:html-dom), here are example of actions you will be able to perform: inspect the HTML structure of the page, debug JavaScript code as demonstrated in \@ref(shiny-js-inspector), inspect served files (static assets like CSS, JS, images), run performances audit \@ref(mobile-pwa), inspect websocket activity (section \@ref(shiny-intro)) and many more.
In this section, we restrict the description to the first panel (Elements) of the __developer tools__. This feature is available in all web browsers; however, for demonstration purposes, we will only focus on the [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools). It may be opened:
- After a right click and selecting inspect.
- After clicking on ctrl + shift (Maj) + I for Windows, option + command + I on Mac.
In the following:
- Open the hello-world.html example with Chrome.
- Right-click to open the HTML inspector (developer tools must be enabled if it is not the case).
The HTML inspector is a convenient tool to explore the structure of the current HTML page. On the left-hand side, the DOM tree is displayed where we clearly see that `<html>` is the parent of `<head>` and `<body>`. `<body>` has also one child, that is `<p>`. We may preview any style (CSS) associated with the selected element on the right panel as well as Event Listeners (JavaScript), which will be discussed later in the book.
```{r html-dom, echo=FALSE, fig.cap='Inspection of the DOM in the Hello World example.', out.width='100%'}
knitr::include_graphics("images/survival-kit/dom.png")
```
#### Web Inspector 101
In the following, we enumerate key features of the inspector `Elements` tab. In addition to exploring the HTML [structure](https://developers.google.com/web/tools/chrome-devtools/dom), the inspector allows you to:
- Dynamically change CSS at run time.
- Debug JavaScript code (put break points, ...).
- Run JavaScript code from the console.
- Monitor any error or warning that may prevent your app or website from properly working.
Another important feature is the ability to switch between different devices, especially mobile platforms and do a global performance audit with [Google LightHouse](https://developers.google.com/web/tools/lighthouse). The [book](https://engineering-shiny.org/when-optimize.html#tools-for-profiling) from Colin Fay et al [@thinkrShiny]. details the most relevant elements for Shiny app development.
We propose a set of quick exercises to review the most important HTML inspector capabilities that are commonly needed during Shiny app customization. We consider the app already defined above:
```{r, eval=FALSE}
ui <- fluidPage(p("Hello World"))
server <- function(input, output, session) {}
shinyApp(ui, server)
```
##### Exercise: Altering the DOM structure
1. Run the `Hello World` app, right-click on the only text element and select inspect.
2. Notice we could have done similarly by clicking on the very top-left corner `Inspect` icon (command + maj + C for Mac) and hovering over the `Hello World` text.
You should see a result similar to Figure \@ref(fig:html-dom-inspect), the `Inspect` icon being in blue. The selected element is highlighted, and a white box displays the main CSS properties like `text-color`, `font-size`, margins, as well as accessibility parameters.
```{r html-dom-inspect, echo=FALSE, fig.cap='Inspection of the p element in the Hello World example.', fig.align = 'center', out.width='100%'}
knitr::include_graphics("images/survival-kit/dom-inspect.png")
```
3. In the `Elements` panel, double-click between the `<p>` and `</p>` tags to edit the current text. Press enter when finished.
4. Let's add some children to our `p` tag. Right-click and select the `Edit as HTML` option. You may enter any valid HTML code inside. Don't forget about some rules relative to inline and block tags (inline tags cannot contain block tags!!!).
As depicted in Figure \@ref(fig:html-dom-edit), we could have done a right click on the `p` tag to display more options like:
- __Add/edit__ an attribute. You may try to add a class `class="awesome-text"` and an id `id="only-text"`.
- __Delete__ the current tag (the `delete` key would do it as well).
- __Copy__ the element with all nested elements.
- Only copy the outside HTML (ignore nested elements).
- __Extract__ the CSS selector or JavaScript path (code to select the element): `body > div > p` and `document.querySelector("body > div > p")`, respectively. These two features are extremely handy as they save you time. Try to copy and paste `document.querySelector("body > div > p")` in the JavaScript console at the bottom of the inspector window. It returns the selected HTML element, as shown in Figure \@ref(fig:html-dom-js-path)! Amazing isn't it?
- __Hide__ the element.
- __Force__ a specific state. For instance buttons may be `active`, `inactive`. We talk more about this in section \@ref(css-pseudo-classes).
```{r html-dom-edit, echo=FALSE, fig.cap='Modifications of the p element in the Hello World example.', fig.align = 'center', out.width='40%'}
knitr::include_graphics("images/survival-kit/dom-edit.png")
```
```{r html-dom-js-path, echo=FALSE, fig.cap='Extract the JavaScript path to select the p element.', fig.align = 'center', out.width='80%'}
knitr::include_graphics("images/survival-kit/dom-js-path.png")
```
Whenever you are looking for a specific tag in a more complex page, the `search tag` option is a game changer (Ctrl + F on Windows, command + F within the Elements tab on a Mac). See Figure \@ref(fig:html-dom-search).
```{r html-dom-search, echo=FALSE, fig.cap='Search for element having the awesome-text class.', fig.align = 'center', out.width='50%'}
knitr::include_graphics("images/survival-kit/dom-search.png")
```
Finally, the inspector toolkit allows you to reorder DOM elements with a rather intuitive drag and drop feature. I invite the reader to take some time to experiment with those features as they will be crucial in the next chapters, particularly Chapter \@ref(beautify-css).
### Preliminary introduction to CSS and JavaScript
To introduce this section, I propose looking at the very first website, early in the 1990's (August 1991 exactly). From an aesthetic point of view (see Figure \@ref(fig:www-first)), this is far from what we can observe today as shown in Figure \@ref(fig:www-rinterface).
```{r www-first, echo=FALSE, fig.cap='World Wide Web website.', out.width='100%'}
knitr::include_graphics("images/survival-kit/www-first.png")
```
```{r www-rinterface, echo=FALSE, fig.cap='RinteRface website: (https://rinterface.com).', out.width='80%', fig.align='center'}
knitr::include_graphics("images/survival-kit/www-rinterface.png")
```
<!-- How does explaining this history help the reader? -->
How can we explain that difference? One of the main reasons is the absence of CSS (Cascading Style Sheets) as the first CSS release only appeared in December 1996, that is five years later than the first web site publication. CSS allows you to deeply customize the appearance of any web page by changing colors, fonts, margins and much more. We acknowledge that the role of JavaScript cannot be demonstrated through the previous example. Yet its impact is as important as CSS, so that it is now impossible to dissociate HTML, CSS and JavaScript.
#### HTML and CSS {#html-and-css}
CSS changes the style of HTML tags by targeting specific classes or ids. For instance, if we want all `p` tags to have red color we use:
```{r, echo=FALSE, results='asis'}
css_code <- "p {
color: red;
}"
code_chunk_custom(css_code, "css")
```
To include CSS in an HTML page, we use the `<style>` tag as follows:
```{r, echo=FALSE, results='asis'}
html_code <- '<!DOCTYPE HTML>
<html lang="en">
<head>
<style type="text/css">
p {
color: red;
}
</style>
<title>A title</title>
</head>
<body>
<p>Hello World</p>
</body>
</html>'
code_chunk_custom(html_code, "html")
```
You may update the hello-world.html script and run it in your web browser to see the difference. The example may be slight but shows how we may control the look and feel of the display. In a development context, CSS files may so big that it is better to include them in external files.
<!-- Would be good to show how you can use the web inspector to experiment interactively -->
Let's build a Shiny app that does similar things. As a reminder, you may use `tags$style` to include small pieces of CSS in your app:
```{r, eval=FALSE}
ui <- fluidPage(
tags$style("p { color: red;}"),
p("Hello World")
)
server <- function(input, output, session) {}
shinyApp(ui, server)
```
Be prepared! In Chapter \@ref(beautify-css), we'll dive into CSS and expose best practices.
#### HTML and JavaScript
JavaScript is a game changer to give life to your web apps. It is an object-oriented programming (OOP) language allowing interaction with the HTML elements.
In the following example, we defined the `changeColor` function that targets the element having `hello` id and change its color property to green. The HTML element has an `onClick` attribute that triggers the `changeColor` function each time the button is clicked.
```{r, echo=FALSE, results='asis'}
html_code <- '<!DOCTYPE HTML>
<html lang="en">
<head>
<style type="text/css">
p {
color: red;
}
</style>
<script language="javascript">
// displays an alert
alert(\'Click on the Hello World text!\');
// change text color
function changeColor(color){
document.getElementById(\'hello\').style.color = color;
}
</script>
<title>A title</title>
</head>
<body>
<!-- onclick attributes applies the JavaScript
function changeColor define above -->
<p id="hello" onclick="changeColor(\'green\')">Hello World</p>
</body>
</html>'
code_chunk_custom(html_code, "html")
```
In a few lines of code, you can change the color of the text and this is only the beginning.
We see below that the process is not dramatically different in a Shiny app. We wrap our custom JavaScript in the `tags$script` function, as below:
```{r, eval=FALSE}
ui <- fluidPage(
tags$script(
"alert('Click on the Hello World text!');
// change text color
function changeColor(color){
document.getElementById('hello').style.color = color;
}
"
),
p(id = "hello", onclick="changeColor('green')", "Hello World")
)
server <- function(input, output, session) {}
shinyApp(ui, server)
```
If you are not already familiar with JS, Chapter \@ref(survival-kit-javascript) provides some basic knowledge to unleash interactivity in your Shiny apps.
## Summary
As demonstrated above, developing a Shiny app is basically building a website from R and is completely compatible with the web languages, that is, HTML, CSS and JavaScript. In the next chapter, we'll discover how to manipulate HTML tags from R with the help of `{htmltools}` [@R-htmltools], to seamlessly customize any existing Shiny element but also import any external HTML template.