Super Solutions for Shiny Architecture 2/5: Javascript Is Your Friend

[This article was first published on r – Appsilon Data Science | End­ to­ End Data Science Solutions, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

TL;DR

Three methods for using javascript code in Shiny applications to build faster apps, avoid unnecessary re-rendering, and add components beyond Shiny’s limits. Part 2 of a five part series on super solutions for Shiny architecture. 

Why Javascript + Shiny? 

Many Shiny creators had a data science background, and not a programming background and are not familiar with javascript (including me). That is fine – most of the actions you imagine for your app can be operated via native Shiny code. The problem begins when the imagination of the Product Owner or whoever is designing features goes way beyond what Shiny can do ????  At Appsilon we recommend using pure javascript heavily in Shiny applications as it is just much more effective and less restricting. This post will explain ‘why’ and ‘how’. I’m sure that going down the road of Shiny development you will quickly figure out that it is needed on your own.

Why use javascript in your apps? First of all, it does not limit you as much as pure Shiny – at the end most of the Shiny functions are javascript wrappers with some narrowed functionality. What is more it is just faster – especially when some loops are used. Omitting the reactivity chain may let you avoid a lot of troubles. Last but not least – javascript allows you to operate on existing DOM elements while Shiny often re-renders objects (although functions from the update family try their best to avoid it). 

Let’s take a look at this short example. We would like to modify the standard DT example of styling tables by changing all of the negative values to be red, just to be easier to distinguish them. Maybe such an option exists in pure Shiny DT somewhere – I did not even bother looking for it, as using JS is so much easier and faster. Also if this action should be triggered on some user click, the table would be re-rendered in Shiny and not in JS – we are saving the user’s precious time. Using javascript here is super easy: just define the elements that you’re interested in – the cells of the table – and run the naive loop with the desired condition: 

Super Solutions for Shiny Architecture

(Note that in the gif, it is run in a browser console, but after reading this post you’ll know how to implement the same action in the Shiny app.)

Our general advice would be: when you struggle in doing something with Shiny or it is super inefficient, search for a javascript solution; probably the answer is already posted somewhere (yes, using stackoverflow is not shameful for a programmer, you can google a lot with no worries) and all you need to know is how to implement the JS in your Shiny app. Well, let’s explain how.

There are basically three ways to incorporate javascript in your Shiny app – choosing one or another basically depends on the amount/reproducibility of code you’ll be using and your javascript expertise. Surprisingly the most advanced method is the simplest to implement, but from our experience we know that for pure R programmers it is not easy to switch directly to javascript, thus the first two methods might be a good starting point for ease of implementation. 

Method 1: Just call the javascript inline

Here the great help comes with the shinyjs package by Dean Attali. All you need to do is wrap the pure JS code given as a string into shinyjs::runjs() and use it as a replacement for your standard R solution (please do remember to initialize the package in your ui file with useShinyjs() – check package description for details). The main advantage of this method is how easy it is to incorporate the R objects that you’re familiar with: put them inside the string passed as JS code (we encourage using something like sprintf or glue to do this, not a series of paste). Here is an example of the usage for the gif:

observeEvent(input$color_cells, {
    color.negative <- “red”
    shinyjs::runjs(
      sprintf("
              var cells = document.getElementsByTagName('td');
              for(i = 0; i < cells.length; i++) {
                if(cells[i].textContent < 0) {
                  cells[i].style.color=%s;
                }
              }
              ", color.negative
      )
    )
  })

 

Method 2: Extend Shinyjs functionality

extendShinyjs is another great tool provided by Dean Attali with the feature of simple passing of R parameters into JS code. Use this method when you need a long and reusable javascript code (JS will be in regular script, not inline) but you still want to operate inside JS easily with R objects.

Implementing it into your Shiny app requires the following steps:

1. Create a JS file named shinyjs.functionName.js (functionName to be replaced with your functionality) in the dedicated folder in your app (usually shiny/www/js)

2. Define the content of the file as follows – some boilerplate code is needed at the beginning. (of course the names, number and default values of the parameters are up to you):

shinyjs.functionName = function(params) {

  var default_params = {
    param_1 : null,
    param_2 : null,
  };

  params = shinyjs.getParams(params, default_params);
    param_1 = params.param_1;
    param_2 = params.param_2;
// the actual JS code goes here
};

3. Add the JS extension to your ui file, in your dashboardBody/semanticPage/mainPanel, namely:

tags$head(
  extendShinyjs(
    script = "www/js/shinyjs.functionName.js",
    functions = "functionName"
  )
 )

(note: there can be more than a single function defined per file, but to organize the code better we recommend keeping them separated and naming file the same as functions)

4. Now you can use the function! Wherever you wish in the code (e.g. in observe or observeEvent) just call js$functionName() and set (in this test case) the two defined parameters, param_1, param_2

As you can see, this method requires a little more effort when defining in comparison to inline JS code, but it hides your JS code in the function, thus making the code clearer and allows you to use the same function in many places, thus making your code more DRY.

Method 3: Attach the JS script

The most advanced javascript solutions are built just as scripts, independent of the R code and variables. Also in that form many ready-to-use solutions will be available. All you need to do is:

  1. Save the js file in the dedicated folder
  2. Call it in the ui body as tags$head(tags$script(src = “js/myScript.js”)) with whatever name it has.

From now on, all of the javascript code is attached to your application and will be launched on app start or when called, depending on the functionality. 

Simple? I guess so, but practice makes you a master. If you have no experience with javascript, start experimenting with replacing your R code with JS and check whether it improves the performance. Or take some functionality from your  ‘dream’ shelf that you thought was impossible to achieve with Shiny and try implementing it with JS. Good luck!

Bonus: Shiny also has some JS functions that can be called directly from the browser without running through the session in server (thus making it more efficient). Check “Communicating with Shiny via JavaScript” for R-JS communication or try running javascript code in your app:

 Shiny.notifications.show({html: “Shiny JS code!”, type: “message”, duration: 2000, closeButton: true})

– the result should be the same as using R code to generate notifications.

Thanks for reading!  Follow me on Twitter @dubelmarcin.  You can also check out the first post in the series, Super Solutions for Shiny Architecture 1 of 5: Using Session Data. And don’t forget to sign up for our newsletter!

Article Super Solutions for Shiny Architecture 2/5: Javascript Is Your Friend comes from Appsilon Data Science | End­ to­ End Data Science Solutions.

To leave a comment for the author, please follow the link and comment on their blog: r – Appsilon Data Science | End­ to­ End Data Science Solutions.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)