20  JavaScript

Caution

The contents for section are under development. Thank you for your patience.

This chapter will cover how to include JavaScript in your Shiny app-package. Learning a little JavaScript is the quickest way to improve the quality of your Shiny apps (most of the code in a rendered Shiny app is JavaScript). We’ll only cover a few examples here, but I’ve included links for more resources in the callout box below.

Beginners:

R-flavored JavaScript:

Shiny-focused JavaScript:

R & JavaScript

R and JavaScript have different strengths–R is more accessible for anyone familiar with statistics and math, while JavaScript’s syntax will be more recognizable to those with experience programming in languages like Java and C. JavaScript has unique behaviors (like asynchronous programming and dynamic typing) which can make it challenging, but it’s widely taught due to its essential role in web technologies.

R tends to be on the slower end when compared to other languages, but it works well for stats and it can be sped up with packages (and external languages like C++). JavaScript is fast, especially in web browsers and Node.js, thanks to advanced optimization in modern JavaScript engines like V8.

Both languages have strong communities and ecosystems. R is popular among academics and data professionals 1, while JavaScript has a massive developer community due to its role in web development and supported by many frameworks and libraries.2

R is often developed in IDEs like Posit Workbench or Jupyter Notebooks, while JavaScript is commonly used with text editors such as Visual Studio Code, WebStorm, along with tools like Babel and Webpack.

To recap: R is well suited for statistical analysis and data science, while JavaScript is used for both client and server-side web development, making it useful for creating interactive web pages, as well as mobile and desktop applications.

I’ve created the shinypak R package In an effort to make each section accessible and easy to follow:

Install shinypak using pak (or remotes):

# install.packages('pak')
pak::pak('mjfrigaard/shinypak')

Review the chapters in each section:

library(shinypak)
list_apps(regex = 'js')
## # A tibble: 2 × 2
##   branch            last_updated       
##   <chr>             <dttm>             
## 1 19.1_js-htmltools 2024-05-20 21:09:58
## 2 19.2_js-react     2024-05-09 06:56:26

Launch an app with launch()

launch(app = "19.1_js-htmltools")

Download an app with get_app()

get_app(app = "19_js")

20.1 JavaScript & htmltools

The first example we’ll use is covered in the Shiny documentation and uses the htmlDependency() function from the htmltools package.3 htmltools is maintained by Posit/RStudio, and it’s one of the underlying workhorses of Shiny, containing “tools for creating, manipulating, and writing HTML from R.

htmlDependency() is typically used within a function and included in the standalone app function with the following arguments:

name

The name of JavaScript library.

version

The version of the library or resource we are including.

src

The location of the JavaScript script (in the inst/ folder).

script

The name of the script file (or files).

Alerts

We’ll define a simple JavaScript file below, alert.js:

document.addEventListener("DOMContentLoaded", function() {
  alert("Shiny App with JavaScript!");
});

Launch app with the shinypak package:

launch('19.1_js-htmltools')

Below is a createJsDependency() function with the arguments described above:

#' Create JavaScript Dependency for Shiny App
#'
#' This function creates an `htmltools` dependency object that includes a 
#' JavaScript file. 
#' 
#' @return A dependency object that can be included in a Shiny app's UI.
#'
#' @export
#' 
createJsDependency <- function() {
  htmltools::htmlDependency(
    name = "my-js",
    version = "1.0",
    src = c(file = system.file("js", package = "moviesApp")),
    script = "alert.js"
  )
}

Note the name and version values are arbitrary and are chosen for illustrative purposes. The createJsDependency() is placed in R/ with the other package functions and alert.js is placed in inst/js:

├── R
│   └── createJsDependency.R
└── inst/
     └── js/
          └── alert.js

We can use createJsDependency() inside the fluidPage() function in movies_ui():

  fluidPage(
    theme = shinythemes::shinytheme("spacelab"),
    # Include the JavaScript dependency
    createJsDependency(),
    titlePanel(

After loading, documenting, and building our app-package, we’ll launch to check the new functionality:



Ctrl/Cmd + Shift + L / D / B

library(moviesApp)
launch_app(run = 'b')

JavaScript alert

This setup encapsulates the dependency definition with the app’s UI definition, ensuring the JavaScript is loaded every time the app is started.

Bookmarking

Our next example has been adapted from the Shiny examples GitHub repo.4 We’re going to use reactR package to add bookmarking functionality to our Movies app. I’ve placed a small development version of this application in the inst/ folder:

inst/
  └──js-app/
        ├── R/
           ├── mod_text_input.R
           ├── mod_text_output.R
           └── simpleTextInput.R
        ├── app.R
        └── js/
            └── input.js

3 directories, 5 files

Launch app with the shinypak package:

launch('19.2_js-react')

The js-app folder contains two modules (mod_text_input and mod_text_output):

js-app modules
# Text Input Module UI ----
mod_text_input_ui <- function(id) {
  ns <- NS(id)
  tags$p(simpleTextInput(ns("simpleTextInput")))
}

# Text Input Module Server ----
mod_text_input_server <- function(id) {
  moduleServer(id, function(input, output, session) {
    return(
      reactive(input$simpleTextInput)
      )
  })
}
# Text Output Module UI ----
mod_text_output_ui <- function(id) {
  ns <- NS(id)
  textOutput(ns("simpleTextOutput"))
}

# Text Output Module Server ----
mod_text_output_server <- function(id, text) {
  moduleServer(id, function(input, output, session) {
    output$simpleTextOutput <- renderText(text())
  })
}

The modules communicate with a utility function, simpleTextInput():

js-app utility function
simpleTextInput <- function(inputId, default = "") {
  createReactShinyInput(
    inputId = inputId,
    class = 'simple-text-input',
    dependencies = htmltools::htmlDependency(
      'simple-text-input',
      '1.0.0',
      src = file.path(getwd(), "js"),
      script = 'input.js',
      all_files = FALSE
    ),
    default = default,
    container = tags$span
  )
}
1
note we aren’t using system.file() here because we’re referencing this script locally

simpleTextInput utilizes the React component by calling createReactShinyInput. It specifies the input ID, the CSS class, and the dependencies with htmltools::htmlDependency() (including the path to the input.js script) to return a React component embedded within Shiny’s UI framework.

The js/ folder contains the input.js script, which we’ll describe next.

Defining a React Component

Let’s examine how these files work together to create the bookmarking functionality. The input.js JavaScript file contains a function designed to integrate React components into an R Shiny application. It utilizes the reactR package, which bridges R and React.js by enabling the use of React components as Shiny inputs.

The JavaScript function SimpleTextInput in input.js is defined to create a simple text input field. This functional React component receives props (properties) from the parent component or from Shiny:

(function() {
  function SimpleTextInput(props) {
  }
}

Create React Element

React Component Structure: The component uses React.createElement to create an <input> element. This method takes at least three arguments:

  • The type of the element ('input' in this case).

    (function() {
      function SimpleTextInput(props) {
        return React.createElement('input', {
    
        });
      }
  • Properties (props) of the element, which include:

    • value is the current value of the input, which is provided by props.value.

      (function() {
        function SimpleTextInput(props) {
          return React.createElement('input', {
            value: props.value,
      
          });
        }
    • onChangeis a function that updates the state (in Shiny) whenever the input changes. This function captures the new value from the input field and passes it back to Shiny via props.setValue.

      (function() {
        function SimpleTextInput(props) {
          return React.createElement('input', {
            value: props.value,
            onChange: function(e) {
              props.setValue(e.target.value);
            }
          });
        }

Integration with Shiny

The reactR.reactShinyInput method is called to bind the SimpleTextInput React component to Shiny. This binding process includes:

  • A CSS selector ('.simple-text-input') that identifies the HTML element in the Shiny UI to be replaced by this React component.

      reactR.reactShinyInput('.simple-text-input');
  • A namespace ('shiny.examples'), typically used for managing JavaScript namespaces in larger applications.

      reactR.reactShinyInput('.simple-text-input', 'shiny.examples');
  • Passing the SimpleTextInput function itself, which defines the behavior and structure of the React component.

      reactR.reactShinyInput('.simple-text-input', 'shiny.examples', SimpleTextInput);
    ))();

The final input.js script in inst/js-app/ is below:

(function() {
  function SimpleTextInput(props) {
    return React.createElement('input', {
      value: props.value,
      onChange: function(e) {
        props.setValue(e.target.value);
      }
    });
  }
  reactR.reactShinyInput('.simple-text-input', 'shiny.examples', SimpleTextInput);
})();

Usage in app.R

In the R Shiny application (app.R), we include the bookmarkButton() in the bslib::sidebar() with the input module, and place the output module in the bslib::card():

js-app app.R file
# packages
library(shiny)
library(reactR)
library(htmltools)
library(bslib)
  
# app ui
ui <- function(request) {
  bslib::page_fillable(
    h1("MoviesApp Bookmark Demo"),
    bslib::layout_sidebar(
      sidebar =
        bslib::sidebar(
          tags$h3("Text Input"),
          mod_text_input_ui("txt_in"),
          tags$p(
            bookmarkButton()
            )
        ),
      bslib::card(
        full_screen = TRUE,
        bslib::card_header(
          tags$h3("Text Output")
        ),
        mod_text_output_ui("txt_out"),
        bslib::card_footer(
          tags$em(
            "Adapted from ",
            tags$a(
              href = "https://github.com/rstudio/shiny-examples/tree/main/151-reactr-input",
              "Shiny-examples"
            )
          )
        )
      )
    )
  )
}
  
# app server
server <- function(input, output, session) {
  
  text_val <- mod_text_input_server("txt_in")
  
  mod_text_output_server("txt_out", text_val)
  
}

shinyApp(ui, server, enableBookmarking = "url")
1
simpleTextInput() is used within mod_text_input_ui() to define a text input element in the ui layout.
2
The text entered into mod_text_input_ui() is captured reactively via Shiny through mod_text_input_server()
3
…where it can be used in the server logic and displayed the text output module (mod_text_output_server())

20.2 Recap

This example comes from the charpente package.5


  1. R is favored in academic circles and among data professionals, and it has many packages, such as ggplot2 for data visualization and dplyr for data manipulation.↩︎

  2. JavaScript commands a massive developer community due to its pivotal role in web development, supported by numerous frameworks and libraries like React, Angular, and Vue.js that facilitate web development tasks.↩︎

  3. See the article section titled, htmlDependency object↩︎

  4. Shiny examples contains over 100 applications demonstrating various features, layout, functionalities, etc.↩︎

  5. The charpente package is also covered in Outstanding User Interfaces with Shiny↩︎