--- title: "Literate JavaScript Programming" output: js4shiny::html_document_js vignette: > %\VignetteIndexEntry{Literate JavaScript Programming} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{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, include = FALSE} 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()`. ````markdown --- 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](#json-knitr-engine) and [html](#html-knitr-engine) knitr engines. ```{r eval=FALSE} js4shiny::register_knitr_js_engine() ``` ## Introducing `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. ```{r} x <- 10 message("multiplying x times 20...") x * 20 ``` And here's a JavaScript chunk. ```{js} 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 `
`, 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. ```{js} true && false ``` ```{js} 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. ## Scoping JavaScript Chunks 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. ```{js} 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. ```{js} 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](https://javascript.info/global-object), a recent addition to JavaScript that's available in about [90% of browsers](https://caniuse.com/#search=globalThis). (You could also assign to `window` instead of `globalThis`.) ```{js} globalThis.x = 12 x = x * 4 - 6 ``` If you try to access `x` in another chunk, you'll get the global `x` ```{js} x ``` but you can still create local variables that mask global variables. ```{js} 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 ` ```` 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-` (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](https://livefreeordichotomize.com/2017/01/24/custom-javascript-visualizations-in-rmarkdown/) if your JavaScript chunks are demonstrating [d3.js visualizations](https://d3js.org/). ```{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 zero times. Go ahead and click it! ```{=html}

``` ```{js} 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!` } }) ``` ## Other `console` methods You 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')) ``` ## JSON Chunks {#json-knitr-engine} 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_`, or in this case `data_people`. ```{js} data_people.forEach(function ({first, last, twitter}) { console.log(`- ${first} ${last} (${twitter})`) }) ``` ## HTML Chunks {#html-knitr-engine} 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. ```{html}

This text is green, right?

```