10 JavaScript for Shiny

Design an outstanding interface is not just about making it look nice with HTML and CSS. How do we handle interactivity, widget creation, data flow between R and JS? This is where JavaScript (JS) is our biggest ally. To understand how Shiny works from inside, especially how inputs are handled, we gradually dive into its core which contains a substantial amount of JS. Therefore, this chapter proposes a rather brief introduction to JS and jQuery but still necessary as this book is supposed to be standalone. Advanced JS users may likely skip this part. If you wish to know more about this language, MDN web docs by Mozilla is an excellent resource.

10.1 Shiny JavaScript sources

Since commit 1b8635d, the whole JS core has been converted to TypeScript. As this book was written before these changes, we’ll point the user to the code prior to 1b8635d, that is this state. Practically, the underlying mechanisms remain exactly the same.

Let’s have a look at the shiny (Chang et al. 2021) github project. As a R package, it is composed of standard folders like R, man, tests and other elements. The inst folder contains resources for external dependencies like Bootstrap 3, jQuery, datatables, fontawesome, … mentioned in Chapter 3 sorted in the www/shared sub-folder as well as the whole CSS and JS Shiny codes. Notice the presence of minified files like shiny.min.js and non minified elements such as shiny.css. Overall, the minification process reduces the loading time of a web page by removing comments, extra spaces, thereby decreasing the file size. For instance shiny.js has more than 6500 lines of code (240kb), while shiny.min.js is only 91.4kb.

Notice the srcjs/ folder shown in Figure 10.1. It actually contains all pieces to reconstruct the whole shiny.js file.

Shiny JavaScript sources

FIGURE 10.1: Shiny JavaScript sources

Since in Chapter 12 we’ll use some of those scripts, a little understanding of the basic underlying JavaScript concepts is necessary.

10.2 Introduction to JavaScript

JavaScript was created in 1995 by Brendan Eich and is also known as ECMAScript (ES). Interestingly, you might have heard about ActionScript, which is no more than an implementation of ES by Adobe Systems. Nowadays, JavaScript is the centerpiece of web development across all websites.

Here is a quick example. If you have a personal blog, you probably know Hugo or Jekyll, especially the R interfaces like blogdown (Xie, Dervieux, and Presmanes Hill 2021). These tools allow one to rapidly develop a nice looking blog in just a few minutes, focusing on the content rather than technical aspects, which is really the point! Now, if you open the HTML inspector introduced in Chapter 1.2, click on the elements tab, which may open by default, and uncollapse the <head> tag, you see that a lot of scripts are included, as shown in Figure 10.2. Similarly for the <body> tag.

A website is full of JavaScript

FIGURE 10.2: A website is full of JavaScript

There are three ways to include scripts in an HTML document:

  • Use the <script> tag with the JS code inside.
  • Add the onclick attribute to an HTML tag (preferably a button) to trigger JS as soon as it is clicked.
  • Import an external file containing the JS code and only.
<script type="text/javascript">
// JS code here
</script>
<button id="hello" onclick="jsFunction()">Hello World</button>
<!-- We use the src attribute to link the external file -->
<script type="text/javascript" src="file.js">

Whether to choose the first, second or third method depends on the content of your script. If we consider the JS library jQuery, it unfortunately contains so much code making it a challenge to understand. This make user chose the third method, most of the time.

10.3 Setup

Like R or Python, JavaScript (JS) is an interpreted language. It is executed client-side, in other words in the browser. This also means that JS code may not be run without a suitable tool. In the following, we’ll list some tools to test JS code, even though JS may also be run through the web browser developer tools, as demonstrated in section 10.6.

10.3.1 Node

Node contains an interpreter for JS as well as a dependencies manager, npm (Node Package Manager). To install Node on your computer, browse to the website and follow the installation instructions. Afterwards, open a terminal and check if:

which node
node --version

returns something. If not, Node may not be properly installed.

If you prefer not installing Node, there exists alternatives like repl.it, offering a Node.js online compiler environment. This will be more than enough to follow this part.

10.3.2 Choose a good IDE

Personally, I really like VSCode for coding with JS, as it contains a Node interpreter allowing you to seamlessly execute any JS code. As a side note, I encourage you to try the dracula color theme, which is my favorite! As R user, I also like Rstudio IDE, provided that you have Node installed. Below, we will explain how to run a JS code in both IDE’s. In section 10.6, we will show how to manipulate JS code directly in the web browser, through the HTML inspector. This is the method we will mostly use in the remaining of the book since we will also work with HTML and CSS at the same time.

10.3.3 First Script

Let’s write our first script:

console.log("Hello World");

You notice that all instruction end by ;. You can run this script either in Rstudio IDE or VSCode.

Run JS in VSCode

FIGURE 10.3: Run JS in VSCode

In VSCode, clicking on the run arrow (top center) of Figure 10.3, triggers the node hello.js command, which tells Node to run the script. We see the result in the right panel (code=0 means the execution is fine and we even have the compute time). To run this script in the RStudio IDE, one needs to click on the terminal tab (you could also open a basic terminal) and type node hello.js (or node mycustompath/hello.js if you are not already in the script folder). You should see the Hello World message in the console (see Figure 10.4).

Run JS in a terminal

FIGURE 10.4: Run JS in a terminal

10.4 Programming with JS: basis

We are now all set to introduce the basis of JS. As many languages, JS is made of variables and instructions. All instructions end by the ; symbol.

10.4.1 JS types

JS defines several types:

  • Number. JS does not distinguish between integers and others. In R for instance, numeric contains integers and double.
  • String: characters (‘blabla’).
  • Boolean: true/false.

To check the type of an element, we may use the typeof operator:

typeof 1; // number
typeof 'pouic'; // string

In JS, typeof is not a function like in R!!! Therefore don’t write typeof('string');.

10.4.2 Variables

Variables are key elements to programming languages. They allow to store intermediate results and do other manipulations. In JS, a variable is defined by:

  • A type.
  • A name.
  • A value.

A valid variable name:

  • Doesn’t use a reserved JS name like typeof!
  • Doesn’t start with a number (123soleil)!
  • Doesn’t include any space (total price)!

Besides, code style is a critical element in programming, increasing readability and general consistence. There are several styles, the main ones being snake_case and camelCase. As shown below, there are two ways to create variables in JavaScript.

10.4.2.1 Const

We may use const:

const n = 1;
n = 2; // error
const n = 3; // error
const a;
a = 1; // errors

As shown above, such variables:

  • Cannot be modified.
  • Cannot share the same name.
  • Must be assigned a value.

10.4.2.2 let

Another way to define a variable:

let myVariable = 'welcome';
myVariable = 1;
console.log(myVariable);

All mathematical operators apply, for example:

let myNumber = 1; // affectation
myNumber--; // decrement
console.log(myNumber); // print 0

List of numerical operators in JS:

  • +. + also allows to concatenate strings together.
  • -
  • *
  • /
  • % (modulo)
  • ++ (incrementation)
  • -- (decrementation)

You may also know var to declare variables. What is the difference with let? It is mainly a scope reason:

var i = 1;
{
  var i = 2; // this will modify i globally, not locally
}
console.log(`i is ${i}`); // i is 2.

let j = 1;
{
  let j = 2; // j is only declared locally and not globally!
}
console.log(`j is ${j}`); // j is 1

You will see later that we still use var in the shiny core.

10.4.3 Conditions

Below are the operators to check conditions:

  • === (A equal value, equal type B)
  • == (A equal to B)
  • !== (A not equal value or not equal type B)
  • != (A not equal to B)
  • >, >=
  • <, <=
  • AND (A AND B) or &&
  • OR (A OR B) or ||

Importantly, prefer === and !== to compare elements since 5 == "5" would return true, generally not what you want!

To test conditions there exists several ways:

  • if (condition) { console.log('Test passed'); }
  • if (condition) { instruction A } else { instruction B }

The ternary operator is a shortcut condition ? instruction if true : instruction if false that may be chained. For complex instructions, we recommend not using it, as it may affect code readability.

Whenever a lot of possible conditions have to be evaluated, it is better to choose the switch:

switch (variable) {
  case val1: // instruction 1
  break; // don't forget the break!
  case val2:  // instruction 2
  break;
  default: // when none of val1 and val2 are satisfied
}

10.4.4 Objects

JavaScript is an object oriented programming language (like Python). An object is defined by:

  • A type.
  • Some properties.
  • Some methods (to manipulate properties).

Let’s construct our first object:

const me = {
  name : 'Divad',
  age : 29,
  music : '',
  printName: function() {
    console.log(`I am ${this.name}`);
  }
}

me.geek = true; // works (see const variables above)
// print a human readable object.
console.log(JSON.stringify(me)); 
  
console.log(me.name);
console.log(me.age);
console.log(me.music);
// don't repeat yourself!!!
for (let key in me) { // here is it ok to use `in`
 console.log(`me[${key}] is ${me[key]}`);
}

me.printName();

me = {
  name: 'Paul',
  age: 40
} // error (see const variables above)

Some comments on the above code:

  • To access an object property, we use object.<property>.
  • To print a human readable version of the object, JSON.stringify will do the job.
  • We introduced string interpolation with ${*}. * may be any valid expression.
  • Methods are called with object.<method>. We use this to refer to the object itself. Take note, we will see it a lot!

In JavaScript, there are already predefined objects to interact with arrays, dates, …

10.4.4.1 Arrays

An array is a structure allowing to store information for instance:

const table = [1, 'plop'];
table = [2]; // error
console.log(table);

Array may be nested:

const nested = [1, ['a', [1, 2, 3]], 'plop'];
console.log(nested);

In arrays, elements may be accessed by their index, but as mentioned before, the first index is 0 (not 1 like in R). For instance, if we want to get the first element of the nested array, we do:

console.log(nested[0];
// To get deeply nested element 
// we may chain index
nested[1][1] // Access [1, 2, 3]

Note that the length method returns the size of an array and is very convenient in for loops, as we’ll see later:

nested.length // 3
nested[1].length // 2
nested[1][1].length // 3

Below is a table referencing the principal methods for arrays.

Method/Property Description
length Return the number of elements in an array
Join(string separator) Transform an array in a string
concat(array1, array2) Assemble 2 arrays
pop() Remove the last element of an array
shift() Remove the first element of an array
unshift(el1, el2, …) Insert elements at the beginning of an array
push(el1, el2, …) Add extra elements at the end of an array
sort() Sort array elements by increasing value of alphabetical order
reverse() Symetric of sort()

Among those methods, we mainly use push and length in the next chapters:

table.push('hello'); // [1, "plop", "hello"]

10.4.4.2 Strings

Below are the main methods related to the String object (character in R).

Method/Property/Operator Description
+ (operator) String concatenation
length String length
indexOf() Gives the position of the character following the input string
toLowerCase() Put the string in small letters
toUpperCase() Put the string in capital letters

10.4.4.3 Math

Below we mention some useful methods to handle mathematical objects.

Method Description
parseInt() Convert a string to integer
parseFloat() Conversion to floating number

All classic functions like sqrt, trigonometric functions are of course available. We call them with the Math.* prefix.

10.4.5 Iterations

Iterations allow to repeat an instruction or a set of instructions multiple times. Let’s assume we have an array containing 100000 random numbers. How would you do to automatically print them? This a what we are going to see below!

10.4.5.1 For loops

The for loop has multiple uses. A convenient way to print all array’s elements is to use an iteration:

// ES6 syntax
const nested = [1, ['a', [1, 2, 3]], 'plop'];
for (let i of nested) {
  console.log(i);
}

// or with the classic approach
for (let i = 0; i < nested.length; i++) {
  console.log(nested[i]);
}

The modern approach (ES6) consists in the for, let and of keywords. We define the variable i that will take all values among the nested array and print them. Importantly, i does not refer to the element index, but the element itself! The classic approach shown below uses the index, being slightly more verbose.

Contrary to R, JavaScript index starts from 0 (not from 1)! This is good to keep in mind when we will mix both R and JS.

JS has other methods to do iterations. Let’s have a look at the forEach method for arrays (introduced in ES5):

const letters = ["a", "b", "c", "d"];
letters.forEach((letter) => {
  console.log(letter);
});

Since arrays are objects, it is not surprising to see array.forEach(element, ...).

What for loop should we use? The answer is: it depends on the situation! Actually, there even exists other ways (replace of by in and you get the indexes of the array, like with the first code, but this is really not recommended).

10.4.5.2 Other iterations: while

While loops are another way to iterate, the incrementation step taking place at the end of the instruction:

const h = 3; i = 0;
while (i <= h) {
  console.log(i);
  i++; // we need to increment to avoid infinite loop
}

10.4.6 Functions

Functions are useful to wrap a succession of instructions to accomplish a given task. Defining functions allows programmers to save time (less copy and paste, less search and replace), make less errors and easily share code. In modern JavaScript (ES6), functions are defined as follows:

const a = 1;
const fun = (parm1, parm2) => {
  console.log(a);
  let p = 3;
  // The Math object contains the max method
  return Math.max(parm1, parm2); 
}
let res = fun(1, 2);
console.log(res); // prints a and 2. a global
console.log(p); // fails as p was defined inside the function

This above functions computes the maximum of 2 provided numbers. Some comments about scoping rules: variables defined inside the function, like p, are only available within the function scope, but not outside. It should be noted that functions may use global variables, defined outside like a. In the Shiny JS core, you’ll still find the classic way of defining function:

function initShiny() {
  // do things
}

The main difference being that the ES6 syntax may not be understood by all environments.

10.4.6.1 Export functions: about modules

What happens if you wrote 100 functions that you want to reuse in different scripts? To prevent copying and pasting, we will now introduce the concept of modules. Let’s save the below function in a script utils.js:

const findMax = (parm1, parm2) => {
  return Math.max(parm1, parm2);
}

module.exports = {
  findMax : findMax
}

We create a test.js script in the same folder that calls the findMax function. To do this, we import the corresponding module:

const { findMax } = require('./utils.js');
findMax(1, 2); // prints 2

ES6 introduced another way to import and export element across multiple scripts:

export { findMax, ... }; // in utils.js
import { findMax, ...} from './utils.js'; // in test.js

10.4.7 JS code compatibility

As ES6 is not fully supported by all web browsers, you might wonder how to make your JS code standard. There exists tools called transpiler, that convert any modern JS code into a universal JS code. This is the case of Babel, one of the most commonly used. In chapter 22, we’ll see how to do it with a JS bundler, namelyesbuild.

10.4.8 Event listeners

When you explore a web application, clicking on a button usually triggers something like a computation, a modal or an alert. How does this work? In JavaScript, interactivity plays a critical role. Indeed, you want the web application to react to user inputs like mouse clicks or keyboard events. Below we introduce DOM events.

Let’s consider a basic HTML button:

<button id="mybutton">Go!</button>

On the JavaScript side, we first capture the button element using its id selector with getElementById:.

const btn = document.getElementById('mybutton');

We then apply the addEventListener method. In short, an event listener is a program that triggers when a given event occurs (we can add multiple event listeners per HTML element). It takes two main parameters:

  • The event: click, change, mouseover, …
  • The function to call, also known as callback.
btn.addEventListener('click', function() {
  alert('Thanks!');
});

10.5 jQuery

10.5.1 Introduction

jQuery is a famous JavaScript library providing a user friendly interface to manipulate the DOM and is present in almost all actual websites. It is slightly easier (understand more convenient to use) than vanilla JS. To use jQuery in a web page, we must import its code in the head of our HTML page:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Including jQuery</title>
    <!-- How to include jQuery -->
    <script src="https://code.jquery.com/jquery-3.5.0.js">
    </script>
  </head>
  <body>
   
    <p>Hello World</p>
  
  <script>
    $('p').css('color', 'red');
  </script>
   
  </body>
</html>

10.5.2 Syntax

Below is a minimal jQuery code representing its philosophy (“write less, do more.”):

$(selector).action();

The selector slot stands for any jQuery selector like class, id, element, [attribute], :input (will select all input elements) and many more. As a reminder, let’s consider the following example:

<p class="text">Hello World</p>

To select and interact with this element, we use JavaScript and jQuery:

// vanilla JS
let inner = document.getElementsByClassName('text').innerHTML; 
// jQuery
let inner = $('.text').html(); 

This is of course possible to chain selectors:

<ul class="list">
  <li class="item">1</li>
  <li class="item">2</li>
  <li class="item">3</li>
  <li class="item" id="precious-item">4</li>
</ul>

<ul class="list" id="list2">
  <li class="item">1</li>
  <li class="item">2</li>
  <li class="item">3</li>
  <li class="item">4</li>
</ul>
// Returns an array containing 8 li tags
let items = $('.list .item'); 
// Selects only li tags from the second ul element
let otherItems = $('#list2 .item'); 
// Returns an array with 2 ul elements
let lists = $('ul'); 
// Returns the first li element of the second ul.
let firstItem = $('#list2:first-child'); 

The very good new is that you should already be at ease with CSS selectors from section 6.2!

10.5.3 Good practice

It is recommended to wrap any jQuery code as follows:

$(document).ready(function(){
  // your code
});

// or a shortcut

$(function() {
  // your code
});

This is to avoid to interact with the DOM before it is actually ready. Most of the time, if you forget this, you’ll end up with many issues involving undefined elements.

10.5.4 Useful functions

There exist filtering functions dedicated to simplify item selection. Below are is a list containing the mostly used in Shiny.

10.5.4.1 Travel in the DOM

Method Description
children() Get the children of each element passed in the selector (important: only travels a single level down the DOM tree)
first() Given an list of elements, select the first item
last() Given an list of elements, select the last item
find() Look for a descendant of the selected element(s) that could be multiple levels down in the DOM
closest() Returns the first ancestor matching the condition (travels up in the DOM)
filter() Fine tune element selection by applying a filter. Only return element for which the condition is true
siblings() Get all siblings of the selected element(s)
next() Get the immediately following sibling
prev() Get the immediately preceding sibling
not() Given an existing set of selected elements, remove element(s) that match the given condition

10.5.4.2 Manipulate tags

Below is a list of the main jQuery methods to manipulate tags (adding class, css property…)

Method Description
addClass() Add class or multiple classes to the set of matched elements
hasClass() Check if the matched element(s) have a given class
removeClass() Remove class or multiple classes to the set of matched elements
attr() Get or set the value of a specific attribute
after() Insert content after
before () Insert content before
css() Get or set a css property
remove() Remove element(s) from the DOM
val() Get the current value of the matched element(s)

10.5.5 Chaining jQuery methods

A lot of jQuery methods may be chained, that is like pipe operations in R.

<ul>
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li>Item 4</li>
  <li>Item 5</li>
</ul>

We end the chain by ; and each step is indented by 2 spaces in the right direction:

$('ul')
  .first()
  .css('color', 'green') // add some style with css
  .attr('id', 'myAwesomeItem') // add an id attribute
  .addClass('amazing-ul');

10.5.6 Iterations

Like in JavaScript, it is possible to do iterations with jQuery. Let’s consider the following HTML elements:

<ul>
  <li>Item 1</li>
  <li>Item 2</li>
</ul>

Many jQuery methods have an implicit iteration. For instance, to change the style of each matched element, we don’t need to do as below:

$('li').each(function() {
  $(this).css('visibility', 'hidden'); // Hides all li items
});

Instead, we just have to write:

$('li').css('visibility', 'hidden');

which is much better. The map method has a different purpose. It creates a new object based on the provided one:

const items = [0, 1, 2, 3, 4, 5];
const threshold = 3;

let filteredItems = $.map(items, function(i) {
  // removes all items > threshold
  if (i > threshold) 
    return null;
  return i;
});

10.5.7 Events

In jQuery there exists a significant number methods related to events. Below are the most popular:

$(element).click(); // click event
$(element).change(); // trigger change on an element

// attach an event handler function. 
// Here we add click 
$(element).on('click', function() {
 // whatever
}); 


// one triggers only once
$(element).one('click', function() {
 // whatever
}); 

// useful to trigger plot resize in Shiny so that 
// they correctly fit their container
$(element).resize(); 

// similar to $(element).change();
$(element).trigger('change'); 

The .on event is frequently used in Shiny since it allows to pass custom events which are not part of the JS predefined events. For instance shinydashboard (Chang and Borges Ribeiro 2018) relies on a specific HTML/JavaScript/CSS template including a homemade API for handling the dashboard events.

10.5.8 Extending objects

A last feature we need to mention about jQuery is the ability to extend objects with additional properties and/or method.

// jQuery way
$(function() {
  let object1 = { apple: 0 };
  $.extend(object1, {
    print: function() {
      console.log(this);
    }
  });
  object1.print();
});

With pure JS we would use Object.defineProperty:

// pure JavaScript
Object.defineProperty(object1, 'print', {
  value: function() {
    console.log(this);
  },
  writable: false
});

10.6 Shiny, JavaScript and the HTML inspector

In the above part, we provided some elementary JS knowledge. This section comes back to the main point of this book, that is Shiny. We describe how to leverage the developer tools so as to test, run and debug JavaScript code related to a Shiny app.

10.6.1 The console panel

While developing JS code, we often put some console.log(var) calls to track the content of a given variable and check that our code is doing what it is supposed to do. The resulting messages, errors or warnings are printing in the console, also called a Read-eval-print loop (REPL), suitable to experiment and practice your new JS/jQuery skills.

10.6.1.1 A real REPL

As a warm up, run the shiny app below and open the Chrome DevTools. Notice the two Console tabs (next to Elements and at the bottom, the latter may not be visible all the time and can be activated in the parameters), as depicted in Figure 10.5. We recommend using the bottom one to still see the Elements tab and preview DOM modifications in real time.

ui <- fluidPage()

server <- function(input, output, session) {}

shinyApp(ui, server)
Console panel in the DevTools

FIGURE 10.5: Console panel in the DevTools

Interestingly, you may access any element contained in the window. Copy and paste $("body").addClass("plop"); in the prompt. Notice what happens in the Elements tab.

10.6.1.2 Track errors and warnings

A lot of Shiny app issues on Stack Overflow or in the RStudio community could be more easily solved by quickly inspecting the console.

10.6.2 Debug Shiny/JS code with the inspector

To debug Shiny apps from the inspector, all your scripts have to be in a folder accessible by the app like the www/ folder or by using addResourcePath(). Moreover, if you have minified files, there should be source maps, which will allow to reconstruct the original scripts, that is as they were before the minification process. For instance, Shiny has the shiny.min.js.map. In practice, most R packages bundling HTML templates do not ship these files since they could be quite large (> 1.5MB) and CRAN restricts the package size to 5MB. For instance, the framework7 HTML template, on top of which is built shinyMobile [R-shinyMobile] has source maps but the size can reach 5MB which is obviously too big to include in the R package.

In the following, we consider a very simple shiny app deployed on shinyapps.io, where a notification is displayed with JavaScript as soon as a user clicks an action button. We deliberately made some typos, the goal being to find and fix them.

  1. Browse to the app
  2. Open the Chrome DevTools
  3. Click on the action button (I am pretty sure you clicked before step 2 ;))
  4. As expected and shown Figure 10.6, the console displays an error message: Uncaught TypeError: Cannot read property 'show' of undefined. Sounds good isn’t it?
Error in the console panel

FIGURE 10.6: Error in the console panel

  1. Expand the error message to show the stack trace. We see that the error occurred during an onclick event calling the sendNotif function. Interestingly, we can open this file by clicking on the provided link (notif.js:2). You should get a layout similar to Figure 10.7, depending on your screen width.
Inspect the source causing the error

FIGURE 10.7: Inspect the source causing the error

  1. Let’s briefly describe Figure 10.7. On the left side, you can navigate through all files accessible by the web server, that is shiny internal resources, shiny external dependencies (like Bootstrap 3) as well as your own scripts. If the app is deployed on shinyapps.io, all scripts are located in a folder starting by _w_, which corresponds to the shinyapps.io workerId (this is a detail and not important to understand. See more here). The central part contains any opened script like a classic IDE. The right side displays debugging tools which you may trigger by clicking on the corresponding accordion. The scope shows all variables/object values at a break point, watch allows to track specific elements and Event listener Breakpoints allows to stop at given listener type. We could create a new “watcher” by entering typeof message and clicking the add icon to check the message type within the sendNotif function. Watched expressions are saved when you close the browser.

  2. Put a break point line 2 by clicking on the left side of the center panel and click again on the action button to trigger the break point. I also additionally set 2 Watch Expressions (for message and duration) which type is string and number, respectively, as depicted on Figure 10.8. According to the results, nothing seems wrong for the function arguments.

Inspection of the scope at the breakpoint

FIGURE 10.8: Inspection of the scope at the breakpoint

  1. The error message Uncaught TypeError: Cannot read property 'show' of undefined actually means that notification does not exist. Try yourself by typing Shiny.notification in the console. You’ll get undefined. Instead, the console suggests Shiny.notifications. Let’s replace the wrong code in the notif.js script and then save it. Click on the “Resume script execution” blue button (top left of the right panel). Notice that a notification is displayed and no more error is thrown.

Congrats! You’ve just debugged your first shiny app from the web inspector. In practice, your code is probably much more complex than this example but the workflow remains the same.

10.6.3 The Shiny JavaScript object

The Shiny object is exported at the top of the shiny.js file. In other words, we may use this object and any of its properties within the HTML inspector console tab, in any JavaScript file or shiny app as below:

ui <- fluidPage(
  tags$script(
    "$(function() {
      console.log(Shiny);
    });
    "
  )
)
server <- function(input, output, session) {}
shinyApp(ui, server)

This object contains many properties and methods as shown in Figure 10.9. Some of particular interest, such as like Shiny.setInputValue, Shiny.addCustomMessageHandler, Shiny.shinyapps, Shiny.bindAll, will be detailed later in Chapters 12 and 15.

The Shiny JavaScript object

FIGURE 10.9: The Shiny JavaScript object

At this point, users may find options(shiny.minified = FALSE) convenient to debug the Shiny.js core.

10.7 Exercises

Because the JavaScript console is a REPL, all JavaScript exercises may be done inside, except exercise 3 which also involves HTML. In that case, the reader may browse to jsfiddle.

10.7.1 Exercise 1: define variables

  1. Play with the example below
let myNumber = 1; // affectation
myNumber--; // decrement
console.log(myNumber); // print 0

10.7.2 Exercise 2: define objects

Below is an object skeleton.

const me = {
  name : ,
  age : ,
  music : ,
  printName: function() {
    console.log(`I am ${}`);
  }
}
  1. Fill it with some random values.
  2. Access the name property.
  3. Create the printAge method, which returns the age. Hint: this refers to the object itself. For instance this.name gives the name property.

10.7.3 Exercise 3: jQuery

JSFiddle allows to insert HTML, CSS and JavaScript to test code, share and more. It also does not require you to have any specific configuration on your machine so that you focus on testing!

  1. Go to JSFiddle
  2. Insert the following HTML code chunk in the HTML sub-window.
<!DOCTYPE HTML>
<html>
  <head>
  <!-- head content here -->
  </head>
  <body>
    <ul>
      <li>Item 1</li>
      <li>Item 2</li>
      <li>Item 3</li>
      <li>Item 4</li>
      <li>Item 5</li>
    </ul>
  </body>
</html>

This is a very basic HTML skeleton

  1. In the JavaScript windows, select jQuery 3.4.1 in the dropdown menu (why 3.4.1? The latest Shiny release relies on that version. It is therefore best practice to ensure dependencies are similar, at least the major version).
  2. Since it is best practice to run jQuery code only when the document is ready (avoiding to target non existing elements), we wrap our JS code in the following:
$(function() {
  // your code
});

// or a more explicit syntax
$(document).ready(function() {
  // code
});
  1. Create an event listener to change the third item color as soon as one click on it. Hint 1: To select the a specific item you may use $(selector:eq(i)) where i is the index of the element. Keep in mind that JavaScript starts from 0 and not 1 like R! Hint 2: as a reminder, to create an event listener in jQuery, we use the following pattern.
$("selector").on("event_name", function(e) {
  // your logic
});

10.7.4 Exercise 4: a pure JS action button

Below is another example of a button element with an attached event listener. Clicking on the button will increment its value by 1. Fill in the blanks!

<!DOCTYPE HTML>
<html>
  <head>
  <!-- head content here -->
  </head>
  <body>
    <button>click</button>
  </body>
</html>
$(function() {
    
  // recover the button inner html
  const btnText = ...;

    // event listener for button element
    $(...).click(function() {
    var val = ...;
    // (1) increment button 
    // (2) add the button value to the inner text
    ...
    
    // show alert given condition
    if (val > 3) {
      // do whatever you want
        ...
    }
  });
  
});