17.4 JavaScript <-> {shiny} Communication

Now that we have seen some client-side optimization, i.e. R does not do anything with these events when they happen (in fact R is not even aware they happened).

17.4.1 From R to JavaScript

Calling JS from the server side (i.e. from R) is done by defining a series of CustomMessageHandler functions: these are functions with one argument that can then be called using the session$sendCustomMessage() method from the server side.Or if you are using {golem}, using the invoke_js() function.

Using the skeleton from golem::add_js_handler("first_handler"), we get this snippet:

$( document ).ready(function() {
  Shiny.addCustomMessageHandler('fun', function(arg) {
  
  })
});

Then, you can call it from the server side:

session$sendCustomMessage("fun", list())
# OR
golem::invoke_js("fun", ...)

NOTE: the list() argument will be coverted to JavaScript Object Notation (JSON) and read via JavaScript. If we have function x and variables a and b, then we can use the dot notation of x.a and x.b in our JavaScript.

For Example:

// We define a handler called "computed", that can be called 
// from the server side of the {shiny} application
Shiny.addCustomMessageHandler('computed', function(mess) {
  // The received value (in mess) is serialized in JSON, 
  // so we can  access the list element with object.name
  alert("Computed " + mess.what + " in " + mess.sec + " secs");
})

And within R:

observe({
  # Register the starting time
  deb <- Sys.time()
  # Mimic a long computation
  Sys.sleep(
    sample(1:5, 1)
  )
  # Calling the computed handler
  golem::invoke_js(
    "computed", 
    # We send a list, that will be turned into JSON
    list(
      what = "time", 
      sec = round(Sys.time() - deb)
    )
  )
})

17.4.2 From JavaScript to R

How can you do the opposite (send JavaScript calls to R)?

There is an object called Shiny within your browser. This object can be used to send values to R by creating an InputValue.

// This function from the Shiny JavaScript object
// Allows to register an input name, and a value
Shiny.setInputValue("rand", Math.random())

You bind an input which can be caut fro the server side using:

# Once the input is set, it can be caught with R using:
observeEvent( input$rand , {
  print( input$rand )
})

Shiny.setInputValue cn be used inside any JavaScript function. As an example we use the following code snippet:

  • In inst/app/www/script.js
function alertme(){
  var name = prompt("Who are you?");
  alert("Hello " + name + "! Welcome to my app");
  Shiny.setInputValue("username", name)
}

$(function(){ 
  // Waiting for `{shiny}` to be connected
  $(document).on('shiny:connected', function(event) {
    alertme();
  });
  
  $(".shiny-plot-output").on("click", function(){
    /* Calling the alertme function with the id 
    of the clicked plot. 
    The `this` object here refers to the clicked element*/
    Shiny.setInputValue("last_plot_clicked", this.id);
  });
});

The above snippet gets the user name and last plot clicked. We can catch the call to the server using the following snippet:

# We wait for the output of alertme(), which will set the
# "username" input value
observeEvent( input$username , {
  cli::cat_rule("User name:")
  print(input$username)
})

# This will print the id of the last clicked plot
observeEvent( input$last_plot_clicked , {
  cli::cat_rule("Last plot clicked:")
  print(input$last_plot_clicked)
})

Which will give:

> golex::run_app()
Loading required package: shiny

Listening on http://127.0.0.1:5495
── User name: ─────────────────────────────────────────────────────
[1] "Colin"
── Last plot clicked: ─────────────────────────────────────────────
[1] "plota"
── Last plot clicked: ─────────────────────────────────────────────
[1] "plotb"

NOTE: If you are using modules, you will need to pass the namespacing of the id to be able to get it back from the server. This can be accomplished using the session$ns function. This comes by default using any golem-generated module.

Example of a session$ns call used in Golem Module:

$( document ).ready(function() {
  // Setting a custom handler that will
  // ask the users their name
  // then set the returned value to a Shiny input
  Shiny.addCustomMessageHandler('whoareyou', function(arg) {
    var name = prompt("Who are you?")
    Shiny.setInputValue(arg.id, name);
  })
});
mod_my_first_module_ui <- function(id){
  ns <- NS(id)
  tagList(
    actionButton(
      ns("showname"), "Enter your name"
    )
  )
}

mod_my_first_module_server <- function(input, output, session){
  ns <- session$ns
  # Whenever the button is clicked, 
  # we call the CustomMessageHandler
  observeEvent( input$showname , {
    # Calling the "whoareyou" handler
    golem::invoke_js(
      "whoareyou", 
      # The id is namespaced, 
      # so that we get it back on the server-side
      list(
        id = ns("name")
      )
    )
  })
  
  # Waiting for input$name to  be set with JavaScript
  observeEvent( input$name , {
    cli::cat_rule("Username is:")
    print(input$name)
  })
}