How to get Shiny on the server and JavaScript in the browser talking to each other

On our server we’re programming in R with a variety of add-on packages, primarily Shiny. Users, on the other hand, are all viewing our site using browsers, which run the programming language JavaScript. This article is about how to use a Shiny command to send a message to the user’s browser, telling it to run a JavaScript function. I’ll also include how to get that JavaScript function to send a message back to Shiny (it will appear as a reactive in the input$ list).

If you want to do this, you might find it a lot easier to start with Dean Attali’s shinyJS package, which is available on GitHub, as I did. It supports not only what I’ll show you here, but has a number of useful JavaScript functions. My LEMRS script loads the packages you need to run shinyJS. Those include the V8 package for R and the V8 package for Linux, which give R the ability to run JavaScript code on the server. However, it’s never been clear to me why shinyJS needs to do that. Finding and installing those packages may become a burden (EDIT: in fact, the newest script doesn’t load V8). At the moment, instead of using shinyJS, I’m using the following technique.

So far the only things I’m using JavaScript for are to store and retrieve browser cookies and to redirect the browser to a different URL. I use an existing JavaScript package for managing cookies, js.cookie.js. (I learned about this package from code posted by Callie Gross.) Browsers can track multiple cookies from a single web site, so to set a cookie you need to provide this function with the cookie’s name and value. To retrieve or remove a cookie, on the other hand, you only need to provide the cookie’s name. You load the js.cookie.js code into the user’s browser by including a command in your html page header that tells the browser to load the file. That kind of command looks like this:

 <script src="http://.../js.cookie.js"></script>
 <script src="http://.../open-meta.js"></script>

You would need to replace the … with the full path to the files on your server. The second line loads our own JavaScript code, which is coming up in a minute here. In Shiny, you typically get these commands into your header using code like this in your UI (there’s more detail here).

tags$head(tags$script(src="http://.../js.cookie.js"))

To send a message to the user’s browser you call the Shiny function session$sendCustomMessage(). You have to do this from inside your server function because you need the session object. Without that, you couldn’t direct a message to a particular user’s browser. The session$sendCustomMessage() function takes two parameters. The first parameter is the name of a function you want to call and the second parameter holds the parameters you want to send to that function.

Because this function name is so long and so uninformative, I’ve set up my functions on the R side like this:

js = list()

js$redirect = function(url) {
   session$sendCustomMessage("redirect", url)
}

js$setCookie = function(cookieType, cookie, daysTillExpire=0) {
   session$sendCustomMessage("setCookie", list(
      cookieType=cookieType, 
      cookie=cookie, 
      days=daysTillExpire)
   )
}

js$getCookie = function(cookieType) {
   session$sendCustomMessage("getCookie", list(
      cookieType=cookieType)
   )
}

js$removeCookie = function(cookieType) {
   session$sendCustomMessage("removeCookie", list(
      cookieType=cookieType)
   )
}

I only use this js$ list for storing my R functions that call JavaScript functions. If the function you’re calling only needs a single parameter (or no parameter at all), you just include that parameter as you would in an R function call. For an example, see the first function above, js$redirect. It only needs to know the URL to redirect the user’s browser to. On the other hand, if the JavaScript function has multiple parameters, we pack them all into a named list. The best example of this is the second function above, js$setCookie. Shiny will convert this list into a format called a JSON object using an R package called jsonlite, but it turns out you don’t need to know anything about those details.

Next let’s look at what’s inside the open-meta.js file that I told the browser to load along with js.cookie.js. On the browser side, all of these messages arrive at functions called custom message handlers. Just as you would expect, given the functions on the R side, each JavaScript handler accepts two parameters, a name and a JSON object.

Shiny.addCustomMessageHandler(
         'redirect', function(url) {
   window.location = url;
});

Shiny.addCustomMessageHandler(
         'setCookie', function(pList) {
   if(pList.days>0) {
      Cookies.set(pList.cookieType, escape(pList.cookie), 
         { expires: pList.days });
   } else {
      Cookies.set(pList.cookieType, escape(pList.cookie));
   }
});

Shiny.addCustomMessageHandler(
         'getCookie', function(pList) {
   var cookie = Cookies.get(pList.cookieType);
   if (typeof cookie == "undefined") { cookie = ""; }
   Shiny.onInputChange("js.".concat(pList.cookieType), cookie);
});

Shiny.addCustomMessageHandler(
         'removeCookie', function(pList) {
   Cookies.remove(pList.cookieType);
});

When I’ve packed the parameters into a list, on the JavaScript side I refer to the list as pList and to the individual parameters as pList.name. This is very similar to how you’d refer to the individual items in R, except that you use a dot between the list and the item name rather than a dollar sign.

The technique for getting a message back from JavaScript is shown in the third handler, getCookie. The final line of that function begins Shiny.onInputChange(). Again, not the best named function you’ll find; what it does is send something back to Shiny that will appear in an input$ buzzer. You provide Shiny.onInputChange() two parameters, the name of the input$ buzzer and its value. In the example, “js.”.concat(pList.cookietype) means that the message back to Shiny will show up in the input$js.name buzzer, where name is the value you originally gave to cookieType back in the R code.

As you can see in these examples, you’re not forced to send something back to R; only one of these three functions does. Likewise, it’s not necessary to send something from R to JavaScript to get a message back.

I’ve had problems with my R code calling a JavaScript function too soon. There are probably better and less hackish ways to know when all your JavaScript has finished loading and is safe to call, but here’s what I do. The session$clientData$ variables are reactive triggers that don’t fire until those variables are ready. I just picked the one with the shortest name and set up an observe event to run my initial JavaScript function when it fires. Here’s the code:

 # Wait until JavaScript files have finished loading
   observeEvent(session$clientData$url_port, {
      js$getCookie("sessionID")
   })

In the big picture, what we’re doing here is what Shiny does; sending messages back and forth between the server and the browser. If Shiny natively supports that data transfer, you should use Shiny’s functions. But when you need something Shiny doesn’t natively support, you can fall back to the generic session$sendCustomMessage() and Shiny.onInput Change() functions.

Some other helpful resources on this topic:

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *