Shiny is great for building interactive web apps, and the great thing is: you can start building without any JavaScript knowledge at all. That’s the beauty of Shiny. But you might be looking for something more unique: custom UI elements, animations, adjustments to existing components… That’s where JavaScript comes in! With a little help from JavaScript, you can add that extra spark to your app. In this JavaScript for Shiny tutorial, you’ll learn all the JavaScripts basics you need to you how to get started with JavaScript in your Shiny projects.
Table of Contents
Why JavaScript for Shiny?
JavaScript is a language that interacts with a web page. In this case, that web page is your Shiny app. JavaScript can trigger behaviour: it can change element appearances, hide and show things, click somewhere, show alerts and notifications, and much more. The JavaScript code is executed on the user’s web browser, allowing manipulation and modification of web page content after it’s been loaded. And in an interactive application, there’s lots that’s happening to the content after initial rendering. JavaScript is essential for interactivity.
The thing is, you’re already writing some JavaScript under the hood! In the end, Shiny and related packages take your R code and translate it to HTML, CSS and JavaScript for you. That works fine as long as there are functions that do exactly what you want, but if you’re after other behaviour, you have to come up with solutions yourself. JavaScript solutions, that is.
You don’t necessarily need to become a JavaScript developer or wizard. Knowing about basics like variable declaration, data types, functions, conditional statements, loops, DOM manipulation and events is already a good starting point if you want to manipulate or extent Shiny functionality. So let’s start with the basics!
JavaScript Basics
console.log
Starting with some functionality that will help you get some insight into your code is always a good idea.
console.log() is a method in JavaScript used to output or log information to the browser’s console. It’s an invaluable tool for developers during the development and debugging process. You can simple pass it a message, or a value. It works like print() in R.
In both Safari and Chrome you can right-click and select “Inspect” or “Inspect element” to get to the browser’s console. Not seeing this in Safari? Head over to Settings > Advanced and make sure something along the lines of “Show features for web developers” is enabled.
Variables
Variables are containers for storing data values. You can create a variable in multiple ways:
varis function-scoped and you can re-declare the same variable within the same scope without an error. This means you can override its value in a block within that function, like in a loop or a conditional statement.letis block-scoped and can be reassigned a new value in a block without overriding other, outer, declarations.constis also block-scoped, but cannot be reassigned once it is assigned a value. One small side-note: if the value is an object or array, the contents of the object/array can still be modified.
Below you can clearly see the difference between the three:
// Using var
var x = 10;
if (true) {
var x = 20; // Overwritten
}
console.log(x); // Output: 20
// Using let
let x = 10;
if (true) {
let x = 20; // Block-scoped, doesn't affect outer x
}
console.log(x); // Output: 10
// Using const
const x = 10;
x = 20; // Oops, error!
Data types
JavaScript has different data types like strings (text), numbers, booleans (true or false), arrays, and objects.
let string = "Hello!"
let number = 10;
let isTrue = true;
let fruits = ["apple", "banana", "orange"] // Array
let person = { name: "Jane", age: 25 } // Object
Functions
Just as in R, JavaScript functions are reusable blocks of code. They can take inputs, perform tasks, and return results.
function greet(name) {
return "Hello, " + name + "!"
}
let greeting = greet("Alice");
console.log(greeting) // Output: Hello, Alice!
Conditional statements
Also very similar to R are JavaScript’s conditional statements:
let x = 10;
if (x > 5) {
console.log("x is greater than 5");
} else {
console.log("x is not greater than 5")
}
// Output: x is greater than 5
Loops
It shouldn’t surprise you that there are multiple ways to write a loop in JavaScript. There are numerous ways in R too. The traditional for loop is perhaps the most well known, but another alternative could be forEach or map if you’re working with arrays. And those three are not even the only ones! But for the sake of simplicity, let’s stick to those:
// for loop
// i = 0 is the initialization
// i < 5 is the condition
// i++ is the increment (i = i + 1)
for (let i = 0; i < 5; i++ {
console.log("Count: " + i); // // Output: 0, 1, 2, 3, 4
}
// forEach loop with arrow function
const colors = ["red", "blue", "green", "yellow"]
colors.forEach(color => {
console.log(color); // Output: red, blue, green, yellow
});
// Using map
const arr = [1, 2, 3, 4];
const doubled = arr.map((num) => num * 2);
console.log(doubled); // Output: [2, 4, 6, 8]
DOM manipulation
If you want to use JavaScript for Shiny, you’re probably looking to manipulate your app, aka the web page. It can be about changing the content, structure, or style. We call this DOM manipulation. The DOM (Document Object Model) represents the HTML structure of a webpage as a tree of elements.
In JavaScript, you can target HTML elements. Again, there are various ways to do this, for example by using document.getElementById.
// Let's say the HTML looks like this:
// This is a super simple paragraph
let paragraph = document.getElementById("demo");
paragraph.innerHTML = "Now the content of the paragraph will be different!";
Functions like getElementById are called methods in JavaScript. A method is a function that is associated with an object. In this case, getElementById is a method of the documentobject, which represents the entire HTML document (your full web page).
So, when you call document.getElementById("demo"), you’re using the getElementById method of the document object to find and interact with an HTML element with id “demo”.
You can call methods on the document object, but also on specific elements that you retrieved from the document. To give a few examples:
document.querySelector("#id")to select the first element matching the querydocument.querySelectorAll(".class")to select all elements matching the queryelement.closest("selector")to find the closest ancestorelement.style.property = "value"to change the CSS style
Events
// Let's say the HTML looks like this:
//
let button = document.getElementById("myButton");
button.addEventListener("click", function() {
console.log("Button clicked!");
});
To formalize what events are: events are actions or occurrences that happen in the browser, like when a user clicks a button, types in a text field, or hovers over an image. You can listen for these events and respond to them. This is known as event handling. In the example above, we listen for a click on a certain button with a given id, and when that happens, we send a message to the console.
What is up with the semicolon?
In JavaScript, the semicolon (;) is used to mark the end of a statement, but it’s actually optional in many cases due to a feature called automatic semicolon insertion (ASI).
Sounds nice, but you read “in many cases”. That means not in all cases. ASI sometimes gets confused, because omitting semicolons makes code less clear and less structured. So, you could see the usage of a semicolon as a matter of safety. It also makes your code more readable.
The general rules where a semicolon is involved:
- At the end of a (return) statement
- In a for loop
- To separate statements on one line
If you do this, you’re good 🙂
jQuery
When you’re looking into JavaScript for Shiny, you will also encounter jQuery. But what is it?!
jQuery is a JavaScript library that simplifies common JavaScript tasks. It makes it easier to work with HTML elements, handle events, and perform animations. Things that are quite common in Shiny. jQuery does all of this with shorter, simpler code, and it helps with cross-browser compatibility.
Shiny automatically includes jQuery, and many of Shiny’s built-in functions, like handling input events or updating parts of the UI, use jQuery behind the scenes. This means you can also use jQuery code in your Shiny app to make your app more interactive.
To make a clear distinction between JavaScript and jQuery, you’ll sometimes see the term “vanilla JS”. It is used to refer to the plain, standard version of JavaScript without any libraries or frameworks added on top, like jQuery.
So, with jQuery, you can write less code to achieve more compared to vanilla JS. For example, selecting elements is more concise:
// vanilla JS
document.getElementById("id")
// jQuery
$("#id")
Similarly, attaching an event handler to a button looks quite different in jQuery:
// vanilla JS
document.querySelector("button").addEventListener("click", function() {
alert("Button clicked!");
});
// jQuery
$("button").click(function() {
alert("Button clicked!");
});
Long story short: jQuery comes with some easy selectors and built-in effects, which is nice. That being said, you can do everything with vanilla JS that you can do with jQuery. Modern JavaScript (ES6 and newer) has introduced features like document.querySelector() and classList that make DOM manipulation and handling classes more straightforward. Nowadays, most tasks that once required jQuery for simplicity can now be achieved effectively with vanilla JS.
But… Since jQuery is still more concise and Shiny includes it automatically, you could as well use it! If you start to work more with JavaScript in Shiny you’ll notice that some solutions use vanilla JS and others jQuery. That’s totally fine!
Adding JavaScript to Shiny
With your freshly acquired JavaScript (and jQuery) knowledge under your belt, you might wonder what you should do with it. Where do you add the JavaScript?
And this blog may start to sound like a broken record… But there are multiple ways to add JavaScript to Shiny! We’ll discuss three:
- External (spoiler alert: the preferred one)
- Internal
- Inline
External
In this case, you have an “external” JavaScript file in your www folder. For example, file.js. You then reference it in a script tag that you add to your Shiny application, like so:
ui <- fluidPage(
tags$script(src = "file.js")
)
Another option is to use the includeScript function:
ui <- fluidPage(
includeScript(path = "file.js")
)
The difference? With the latter your file can be in any directory. It is also convenient if you want to use a system file from an R package. You can read more about this difference in the R Shiny documentation.
This method is great if you have a great deal of JavaScript, and want to keep things organized. Having a separate JavaScript file also allows you to edit the file in your IDE of choice which comes with formatting benefits.
Internal
What if you just have a little bit of JavaScript code that you want to add quickly? In that case, you can use the “internal” method. You can also call this inlined script text. You simply wrap your JavaScript code in the HTML function, and put it in a script tag:
ui <- fluidPage(
tags$script(HTML(
"// Your JS code"
))
)
It’s a quick method, with a clear downside: it becomes messy when you include big chunks of JavaScript code. You can’t format it easily, it looks cluttered and it’s hard to share across projects. You can already sense that this is not the preferred method for more complex JavaScript!
Inline
The last method is technically speaking similar to the “internal” method: you add your JavaScript code directly in your UI, and not via an external JavaScript file.
The difference here is that you “append” the code to a certain UI element, and not the full document. For example, you can do that with tagAppendAttributes:
ui <- fluidPage(
tagAppendAttributes(onclick = "alert('Button clicked!');", actionButton("start"))
)
You can use other types of events too. W3Schools explains them nicely.
This method is best for very simple, one-off interactions where you don’t need to write a separate JavaScript function or include a script block.
Shiny events and the Shiny object
You now know the basics of JavaScript and how to add some JavaScript to your Shiny application. So what can you do with that knowledge?
- Add custom actions based on events, like changing appearance on click or hover
- Show alerts or notifications
- Create animations
- Use external JavaScript libraries
- Include basically everything you see on any web page in your Shiny app 🙂
But there’s one thing that we didn’t cover yet: communicating with Shiny. Shiny triggers certain JavaScript events when things are happening on the Shiny side, and Shiny exposes an object that helps communicating with Shiny too: the Shiny object.
There are numerous JavaScript events fired by Shiny, and they all start with shiny:. For example, you have shiny:connected, which is fired when Shiny is connected to the server. These kind of events are handy to keep track of your app’s progress.
The most convenient way to listen for these events is using jQuery:
$(document).on("shiny:connected", function(event) {
console.log("I'm connected to the server!");
});
Other useful events are shiny:busy, shiny:idle, shiny:recalculating and shiny:inputchanged. As the names suggest, these events signal that the Shiny server is busy, idle, recalculating an output or changing an input value. For a full list of Shiny events you can check out the JavaScript for Shiny cheatsheet!
The Shiny JavaScript object provides functions to interact with Shiny’s backend from the web page. It lets send messages to the server, or update Shiny inputs dynamically. For example, you can use Shiny.setInputValue() to change the value of an input from JavaScript!
You can also receive messages in JavaScript from R with Shiny.addCustomMessageHandler. For example, let’s say we want to trigger an alert when a button is clicked. On the JavaScript side, it would look this like:
Shiny.addCustomMessageHandler(
"showAlert",
function(message) {
alert(message.alertText);
}
);
This basically listens for a “showAlert” message. On the Shiny side, you’ll have to send this message via session$sendCustomMessage. You use the same identifier, namely “showAlert”:
session$sendCustomMessage("showAlert", list(
alertText = "This is an alert triggered from R!"
))
})
If you bind the two together, you’ll get the app below. Note that for simplicity, we’re using the internal method (aka, adding the JavaScript in the script tag):
library(shiny)
ui <- fluidPage(
h3("Shiny Custom Alert Example"),
actionButton("alert", "Show Alert"),
# Include the JavaScript directly in the UI using tags$script()
tags$script(HTML("
Shiny.addCustomMessageHandler(
'showAlert',
function(message) {
alert(message.alertText);
}
);
"))
)
server <- function(input, output, session) {
# Observe button click and send a custom message to trigger an alert
observe({
session$sendCustomMessage("showAlert", list(
alertText = "This is an alert triggered from R!"
))
}) |> bindEvent(input$alert)
}
shinyApp(ui, server)
And that’s it! A quick JavaScript for Shiny beginner’s guide that covers the basics of working with JavaScript in Shiny.
Other resources
In this tutorial, we just touched upon the basics. There’s much more to learn about JavaScript for Shiny!
Here are some very good and extensive resources:
- Ultimate JavaScript for Shiny cheatsheet with all Shiny events and common JavaScript and jQuery methods
- Outstanding User Interfaces with Shiny by David Granjon
- JavaScript for R by John Coene
- How to use JavaScript in Shiny by Appsilon
- Engineering Production-Grade Shiny apps by Colin Fay, Sébastien Rochette, Vincent Guyader and Cervan Girard
All the examples and content in this tutorial is part of my online R Shiny course ProductioniZing Shiny, which contains a JavaScript for Shiny module. If you seek deeper JavaScript for Shiny knowledge, you can check out my follow-up course CustomiZing WidgetZ. This follow-up course teaches you all about creating your own UI elements, of course with the help of JavaScript!
Wrap up
You should feel ready to tackle some JavaScript challenges by now! While we just covered the basics, this should already give you a head start for adding some of that JavaScript magic to your app. Hopefully you enjoyed this JavaScript for Shiny tutorial!
☕️ Did you like this tutorial and would you like to show your support? You can buy me a coffee!
📚 Want to learn more about Shiny? Discover my online Shiny courses: ProductioniZing Shiny , CustomiZing WidgetZ and Mobile StructureZ.
I provide R and Shiny consultancy, training and workshops. Do you need help with a project? Or do you want to set up a custom Shiny training? Reach out and let’s have a chat!
