```{css echo=FALSE, eval=identical(Sys.getenv("IN_PKGDOWN"), "true")} div > pre:empty { display: none; }
h1 > code, h2 > code, h3 > code { background: none; color: currentColor; }
pre.r, pre.js, pre.html { position: relative; }
pre.r::after, pre.js::after, pre.html::after { position: absolute; bottom: 0; right: 0; color: #888; text-align: right; padding-right: 5px; width: 50px; }
pre.r::after { content: "r"; } pre.js::after { content: "js"; } pre.html::after { content: "html"; }
.btn-copy-ex { z-index: 100; }
```r knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) js4shiny::register_knitr_js_engine() htmltools::tagList(js4shiny::html_dependency_js4shiny(stylize = "none"))
js4shiny includes several components
that enable the use of R Markdown
for literate programming with JavaScript.
The first of these is the R Markdown HTML format,
js4shiny::html_document_js()
.
--- output: js4shiny::html_document_js ---
Alternatively,
you can override the js
knitr chunk engine
of any HTML-based R Markdown format.
This function also registers json and html knitr engines.
js4shiny::register_knitr_js_engine()
html_document_js
The html_document_js
renders R Markdown to HTML
and uses a new JavaScript knitr engine that
runs each JavaScript chunk in the browser
and writes the output of any console.log()
,
or the return value of the chunk,
into the HTML document, interactively in the browser.
In other words, it renders JavaScript chunks that kind of like R chunks. Here's a standard R chunk.
x <- 10 message("multiplying x times 20...") x * 20
And here's a JavaScript chunk.
const x = 10 console.log("multiplying x times 20...") x * 20
When the document is viewed in a browser,
each JavaScript chunk is evaluated in its own block scope
and any console.log()
statements
and the return value of the block
are automatically printed to the chunk's output <div>
,
unless that value is undefined
.
The last statement in a chunk doesn't need console.log()
to be output.
This makes it easier to show the results of code
without requiring numerous console.log()
statements.
true && false
let x = 12 x = x * 4 - 6
There are a few differences between R chunks and JavaScript chunks. The first is that JavaScript results are always grouped together in the output code block, whereas output from R chunks is printed after the expression causing the output.
The second difference is that R chunks build upon previous chunks; each R chunk is executed in the same environment and the results from each chunk are available to subsequent chunks. The JavaScript chunks, on the other hand, are each executed in a separate block scope, so the result of a chunk isn't necessarily available for later code. I'll explain and provide methods for working around this limitation in the next section.
Because each chunk is block-scoped,
variables created in one block may not be available to other chunks.
Notice that if I try to access x
from the previous JavaScript chunk,
where x
is eventually assigned 42
,
I get an error that x
is not defined.
x
Because variables in JavaScript need to be declared
with let
, const
, or var
,
this may be preferred to executing everything in a shared environment.
For example,
in the first JavaScript chunk in this vignette,
I declared const x = 10
,
but in a later chunk I declared const x = 12
.
If all chunks were evaluated to build upon previous chunks,
then you would frequently run into "identifier already declared" errors.
const x = 42 const x = 64
When you want a variable to exist beyond the scope of the current block,
two methods are available to create global variables.
First, you can assign your variable as a property of
globalThis,
a recent addition to JavaScript that's available in about
90% of browsers.
(You could also assign to window
instead of globalThis
.)
globalThis.x = 12 x = x * 4 - 6
If you try to access x
in another chunk, you'll get the global x
x
but you can still create local variables that mask global variables.
const x = 12 x
Alternatively,
the entire chunk can be evaluated globally
by temporarily disabling the console redirect.
Add js_redirect = FALSE
to the chunk
that you would like to be evaluated in the global scope.
These chunks use the standard js
knitr engine,
that simply embeds the js
code in a <script>
tag in your output.
With this option,
console.log()
output will not appear in the document,
but logged statements will still be available
in the browser's developer tools console.
Helpfully, though,
variables created in these chunks
are assigned in the global scope are thereafter available to all chunks.
```{js, js_redirect = FALSE}`r ''` let globalVariable = 'this is a global variable' console.log(globalVariable) // goes to browser console ``` ```{js}`r ''` console.log(globalVariable) // outputs in document ```
<div>
The live JavaScript chunks are written in to the document as a <div>
and <script>
pair.
For a chunk like the following
```{js hello-world}`r ''` document.getElementById('out-simple-redirect').innerHTML = 'Hello, world!' ```
the following HTML is included in the output.
<div id="out-hello-world"> <pre><!-- output statements will appear here --></pre> </div> <script type="text/javascript"> // the javascript from the current chunk </script>
This means that if your JavaScript chunk needs a dedicated element on the page
to use for writing the output, you can look for the element with id
out-<chunk-name-here>
(non-alphanumeric characters in the chunk name are replaced with _
).
This element exists before the JavaScript is called —
so you can always know that it's available for use —
but after the static code chunk is printed.
This is
particularly useful
if your JavaScript chunks are demonstrating
d3.js visualizations.
```{js hello-world} document.getElementById('out-hello-world').innerHTML = '
Hello, world!
'## Interactive Documents There are a lot of interesting things you can do when you blend your JavaScript and HTML together. For instance, you've clicked the button below <span id="n_btn_clicks">zero times</span>. Go ahead and click it<span id="click-again">!</span> ```{=html} <p><button type="button" id="click-me" value=0 class="btn btn-outline-primary">Click Me</button></p>
const btn = document.getElementById('click-me') btn.addEventListener('click', function() { const val = ++btn.value btn.value = val if (val > 9) console.log("Okay, that's enough!") const text = document.getElementById('n_btn_clicks') text.textContent = val === 1 ? '1 time' : `${val} times` const textCTA = document.getElementById('click-again') if (val === 1) { textCTA.textContent = ' again.' } else if (val >= 10) { textCTA.textContent = ` again. Okay, that's enough, thank you!` } })
console
methodsYou can also use other JavaScript console
methods, like console.table()
.
```{js people-js} const p1 = {first: 'Colin', last: 'Fay', country: 'France', twitter: '@_ColinFay'} const p2 = {first: 'Garrick', last: 'Aden-Buie', country: 'USA', twitter: '@grrrck'}
console.table(p1) console.table(p2)
```{js echo=FALSE} [...document.querySelectorAll('#out-people-js table')] .forEach(el => el.classList.add('table'))
To visualize or use JSON-formatted data in your document,
js4shiny also provides a json
knitr engine.
This next chunk is a JSON chunk named people
that renders as an interactive list.
```{json people}`r ''` [ { "first":"Colin", "last":"Fay", "country":"France", "twitter":"@_ColinFay" }, { "first":"Garrick", "last":"Aden-Buie", "country":"USA", "twitter":"@grrrck" } ] ```
```{json people, echo=FALSE} [ { "first":"Colin", "last":"Fay", "country":"France", "twitter":"@_ColinFay" }, { "first":"Garrick", "last":"Aden-Buie", "country":"USA", "twitter":"@grrrck" } ]
And the data from the JSON becomes a global variable named `data_<chunk_name>`, or in this case `data_people`. ```{js} data_people.forEach(function ({first, last, twitter}) { console.log(`- ${first} ${last} (${twitter})`) })
Finally, js4shiny::html_document_js
also provides an HTML knitr engine.
This is useful when you want the code of the HTML itself to appear in the document,
in addition to the HTML output.
<p style="color: #338D70">This text is green, right?</p>
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.