How to send messages from the browser to the server and back using Shiny

Introduction

In the previous tutorial we learned how to create C3 based widgets for

  • a pie chart
  • a combined line + bar chart, and
  • a stacked area chart.

We mainly focused on constructing these charts from R and sending data in such a way that C3 could animate the transition between the old and new data. This tutorial is all about sending messages from R to JavaScript and back, including how to listen for events.

The ability to send and receive messages greatly improves the capabilities of our widgets, as C3 offers a powerful API with a vast set of options and methods that allow you to modify a chart after it is initialized. For instance, we can use messages to

  • update a legend
  • add or remove data, or
  • focus the viewing area of a chart to a particular subset of its data.

Importantly, we want to be able to do all of this from R. In addition, it want to be able to chain operations together via the pipe operator (%>%) in a way similar to ggplot2 (which uses the + operator).

Messages, events and event listeners

An advantage of JavaScript charts is that they lend themselves well to user interaction. For instance, you can have JavaScript highlight a specific segment of the chart when you hover over the legend. Or, you can have JavaScript remove a segment of the chart when you click on a legend element. Tooltips are another example of chart interaction, in which you can show additional information when hovering over particular elements in the chart.

In JavaScript, things like clicking, hovering, brushing etc. are called events. Often you want JavaScript to do something when an event occurs, i.e. you want to fire a specific function. An event listener, also called an event handler, is a special function that listens for particular events and that fires another function, called a callback function, if JavaScript detects the event. For instance, when you click a button, JavaScript can detect the click event via an event handler, which subsequently can fire another function.

JavaScript has many events that you can listen to, see here for a more complete overview. Luckily, C3.js has an easy mechanism built in that allows you to call specific functions for click events, mouseover, mouseout and drag events. In this tutorial, you’ll learn how to use these.

Sending and receiving messages from Shiny to JavaScript and back

So far we have discussed events that happen solely in JavaScript. Sometimes, however, you may want to:

  1. activate JavaScript functions from R, e.g. to activate a chart method that modifies the chart, or
  2. let Shiny listen to events that happen in the browser.

Let’s see how we can do that!

Shiny offers three pivotal functions for sending and receiving messages. These are:

  1. the R function sendCustomMessage
  2. the JavaScript function Shiny.addCustomMessageHandler
  3. the JavaScript function Shiny.onInputChange.

In the last two items, Shiny refers to a JavaScript object that is provided by Shiny and is available in JavaScript during the lifetime of an app. This object has various methods e.g. addCustomMessageHandler and onInputChange that we can use. Before we use these methods in a C3 context, we first show two basic scenarios that use these functions.

Scenario 1: calling a JavaScript function from Shiny

Many modern R packages like leaflet, the DT package, visNetwork and plotly, use JavaScript functions behind the scenes to perform key functionality. However, as they are all R packages, they invoke such functions from R. How can we do the same? Consider the following figure:

Suppose in JavaScript our main goal is to call the function DoAwesomeThing1 (step 3), which has as an input object message and sends a simple text string to the browser console.

If we want to call this function from R, we can create an event handler in JavaScript (step 2), that handles requests from R, which tells JavaScript to call the function and to pass it a message that it got from R. Suppose we call this handler handler1. In JavaScript, we can create a message handler like this:

Shiny.addCustomMessageHandler("handler1", doAwesomeThing1);

The first argument is the name of our handler, while the second argument is the callback function we want it to execute, i.e. doAwesomeThing1. Whenever this handler gets a message from Shiny, it will pass the message as a function argument and call our JavaScript function doAwesomeThing1. In the example, the function doAwesomeThing1 displays the message via console.log to the browser console.

Finally, in R we can send a message to our handler (which lives in JavaScript) via:

session$sendCustomMessage("handler1", message)

Here, message is a simple text string, e.g. message <- "hello!". To make things more concrete, let’s create an example.

Example 1

Suppose we have a Shiny action button; and when we press it, we want R to send a message string, e.g. “hello!”, to JavaScript. Subsequently, we want JavaScript to create an alert that shows the value JavaScript received from R. The following 4 step procedure shows how we can handle this scenario. The procedure contains an R part (steps 1 & 2, light gray) and a JavaScript part (steps 3 & 4, light blue).

Example calling a JavaScript function from R. Click on the image to see a shiny app implementing this example!

At step 1, we define an action button in ui.R and we include a JavaScript file, i.e. message.js, which we store in the www folder of a Shiny app. The file message.js contains two pieces of JavaScript code, displayed in steps 3 and 4.

In step 2, we create an observeEvent block, which is triggered via the action button. Inside it, we create a message and use session$sendCustomMessage with two arguments. The first argument is the name of the JavaScript message handler we want it to pass our message to. The second argument is the message itself.

Next, on the JavaScript side (step 3) we add our custom message handler via Shiny.addCustomMessageHandler. We’ll give the handler a name (or more formally, a type) equal to handler1 and a callback function, i.e. doAwesomeThing.

Finally, in step 4, we define the function doAwesomeThing, which has a single argument message1, which is subsequently used to raise an alert with the message.

The complete code for this example app can be downloaded here.

Scenario 2: sending a message from JavaScript to Shiny

Instead of sending a message from Shiny to JavaScript, we can also send messages from JavaScript to Shiny. These actions are often coupled to events, e.g. when we want R to do something when we click on an element. Such messages can be sent using the JavaScript method Shiny.onInputChange, which is made available by shiny. Consider the figure below:

In this example, we have a JavaScript function doAwesomeThing2, which takes an id and some data object (this can be anything), who’s task it is to send this information to Shiny. Within the function, here we first create an object with name message, and subsequently use it to send a message back to Shiny. Here, we tell it to make the message available in the R world under the name jsValue. That is, in R we can now listen for events via input$jsValue. So now if doAwesomeThing2 is called, Shiny gets a message. Nice!

Caveat: Shiny only listens for changes in the value of a message. Hence, if you call doAwesomeThing2 twice with the same arguments, the second call will not trigger the observeEvent block because the object you send is unchanged. This can be overcome by adding a random value to your object, which makes the object as a whole appear changed to Shiny. In R, you simply ignore that part of the object. Such a value is sometimes known as a nonce, see here.

For instance in JavaScript via:

var message = {id: "id1", data = [1,2], nonce: Math.random()};

We can force reactivity in repeated calls to doAwesomeThing2 even if the values of id and data don’t change.

Let’s see how we can incorporate these ideas into a small toy app!

Example 2

Let’s say that we have an image on a web page, e.g. the RStudio ball, and when we click on it, we want to send a message from JavaScript to R, indicating that we clicked on the image. To see a Shiny toy app implementing this example, click on the blue RSTUDIO ball below!