Super Solutions for Shiny Apps #2: Javascript Is Your Friend

By:
Marcin Dubel
October 1, 2019

<h2>TL;DR</h2> 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.  <h2>Why Javascript + Shiny? </h2> 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 <i>update</i> family try their best to avoid it).  Let’s take a look at this short example. We would like to modify the standard DT <a href="https://rstudio.github.io/DT/010-style.html">example of styling tables</a> 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:  <img class="aligncenter size-full wp-image-2748" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b022c5da9545dfaaeb69af_jsgif.gif" alt="Super Solutions for Shiny Architecture" width="2860" height="1100" /> (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.) <b>Our general advice would be: when you struggle in doing something with Shiny or it is super inefficient, search for a javascript solution</b>; 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.  <h2><b>Method 1:</b> Just call the javascript inline</h2> Here the great help comes with the <a href="https://deanattali.com/shinyjs/"><i>shinyjs</i></a> package by Dean Attali. All you need to do is wrap the pure JS code given as a string into <i>shinyjs::runjs()</i> and use it as a replacement for your standard R solution (please do remember to initialize the package in your <i>ui</i> file with <i>useShinyjs() -</i> 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 <i>sprintf</i> or <i>glue</i> to do this, not a series of <i>paste</i>). Here is an example of the usage for the gif: <figure class="highlight"> <pre class="language-r"><code class="language-r" data-lang="r">observeEvent(input$color_cells, {    color.negative &lt;- “red”    shinyjs::runjs(      sprintf("              var cells = document.getElementsByTagName('td');              for(i = 0; i &lt; cells.length; i++) {                if(cells[i].textContent &lt; 0) {                  cells[i].style.color=%s;                }              }              ", color.negative      )    )  }) </code></pre> </figure> &nbsp; <h2><b>Method 2: </b>Extend Shinyjs functionality</h2> <a href="https://deanattali.com/shinyjs/extend">extendShinyjs</a> is another <a href="https://deanattali.com/shinyjs/extend">great tool</a> 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 <i>shinyjs.functionName.js</i> (<i>functionName</i> to be replaced with your functionality) in the dedicated folder in your app (usually <i>shiny/www/js</i>) 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): <figure class="highlight"> <pre class="language-javascript"><code class="language-javascript" data-lang="javascript">shinyjs.functionName = function(params) { <br>  var default_params = {     param_1 : null,     param_2 : null,   }; <br>  params = shinyjs.getParams(params, default_params);     param_1 = params.param_1;     param_2 = params.param_2; // the actual JS code goes here }; </code></pre> </figure> 3. Add the JS extension to your <i>ui</i> file, in your <i>dashboardBody/semanticPage/mainPanel</i>, namely: <figure class="highlight"> <pre class="language-javascript"><code class="language-javascript" data-lang="javascript">tags$head(  extendShinyjs(    script = "www/js/shinyjs.functionName.js",    functions = "functionName"  ) ) </code></pre> </figure> (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 <i>observe</i> or <i>observeEvent</i>) just call <i>js$functionName()</i> and set (in this test case) the two defined parameters, <i>param_1</i>, <i>param_2</i>.  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. <h2><b>Method 3:</b> Attach the JS script</h2> 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: <ol><li style="font-weight: 400;">Save the js file in the dedicated folder</li><li style="font-weight: 400;">Call it in the ui body as <i>tags$head(tags$script(src = "js/myScript.js"))</i> with whatever name it has.</li></ol> 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 “<a href="https://shiny.rstudio.com/articles/communicating-with-js.html">Communicating with Shiny via JavaScript</a>” for R-JS communication or try running javascript code in your app: <figure class="highlight"> <pre class="language-javascript"><code class="language-javascript" data-lang="javascript"> Shiny.notifications.show({html: “Shiny JS code!”, type: “message”, duration: 2000, closeButton: true}) </code></pre> </figure> - the result should be the same as using R code to generate notifications. Thanks for reading!  Follow me on Twitter <a href="https://twitter.com/DubelMarcin">@dubelmarcin</a>.  You can also check out the first post in the series, <a href="https://appsilon.com/super-solutions-for-shiny-architecture-1-of-5-using-session-data/">Super Solutions for Shiny Architecture 1 of 5: Using Session Data.</a> And don't forget to sign up for our newsletter!

Have questions or insights?

Engage with experts, share ideas and take your data journey to the next level!
Explore Possibilities

Share Your Data Goals with Us

From advanced analytics to platform development and pharma consulting, we craft solutions tailored to your needs.

Talk to our Experts
r
speed up shiny
tutorials
shiny dashboards