Add JavaScript and CSS in Shiny

Deepanshu Bhalla Add Comment ,
In this tutorial, I will cover how to include your own JavaScript, CSS and HTML code in your R shiny app. By including them, you can make a very powerful professional web app using R.

First let's understand the basics of a Webpage

In general, web page contains the following section of details.
  1. Content (Header, Paragraph, Footer, Listing)
  2. Font style, color, background, border
  3. Images and Videos
  4. Popups, widgets, special effects etc.

HTML, CSS and JavaScript

These 3 web programming languages in conjunction take care of all the information webpage contains (from text to adding special effects).
  1. HTML determines the content and structure of a page (header, paragraph, footer etc.)
  2. CSS controls how webpage would look like (color, font type, border etc.)
  3. JavaScript decides advanced behaviors such as pop-up, animation etc.
Make JavaScript, CSS work for Shiny
Fundamentals of Webpage
One of the most common web development term you should know : rendering. It is the act of putting together a web page for presentation.
Shiny Dashboard Syntax

In this article, I will use shinydashboard library as it gives more professional and elegant look to app. The structure of shinydashboard syntax is similar to shiny library. Both requires ui and server components. However, functions are totally different. Refer the code below. Make sure to install library before using the following program.
# Load Library
library(shiny)
library(shinydashboard)

# User Interface
ui = 
  dashboardPage(
    dashboardHeader(title = "Blank Shiny App"),
    dashboardSidebar(),
    dashboardBody() 
    )

# Server
server = function(input, output) { }

# Run App
runApp(list(ui = ui, server = server), launch.browser =T)

Example : Create Animation Effect

The program below generates animation in the web page. To test it, you can check out this link. When user hits "Click Me" button, it will trigger demojs() JavaScript which will initiate animation. It's a very basic animation. You can edit the code and make it as complex as you want.
HTML
CSS
#sampleanimation {
width: 50px;
height: 50px;
position: absolute;
background-color: blue;
}

#myContainer {
  width: 400px;
  height: 400px;
  position: relative;
  background: black;
}
JS
function demojs() {
  var elem = document.getElementById('sampleanimation');   
  var position = 0;
  var id = setInterval(frame, 10);
  function frame() {
    if (position == 350) {
      clearInterval(id);
    } else {
      position++; 
      elem.style.top = position + 'px'; 
      elem.style.left = position + 'px'; 
    }
  }
}

There are several ways to include custom JavaScript and CSS codes in Shiny. Some of the common ones are listed below with detailed explanation -

Method I : Use tags to insert HTML, CSS and JS Code in Shiny


HTML
tags$body(HTML("Your HTML Code"))
CSS
tags$head(HTML("<style type='text/css'>
Your CSS Code
</style>"))
OR

CSS code can also be defined using tags$style. 
tags$head(tags$style(HTML(" Your CSS Code ")))

JS
tags$head(HTML("<script type='text/javascript'>
Your JS Code
</script>"))

OR

JS code can be described with tags$script.
tags$head(tags$script(" Your JS Code "))

Code specified in tags$head means it will be included and executed under <head> </head>. Similarly tags$body can also be used to make shiny run code within <body> </body>

tags$head vs. tags$body

In general, JavaScript and CSS files are defined inside <head> </head>. Things which we want to display under body section of the webpage should be defined within <body> </body>.

Animation Code in Shiny

library(shiny)
library(shinydashboard)

# User Interface

ui <-

  dashboardPage(
    dashboardHeader(title = "Basic Use of JS and CSS"),
    dashboardSidebar(),
    dashboardBody(

  # Javasript Code
  singleton(tags$head(HTML("
  <script type='text/javascript'>
  function demojs() {
  var elem = document.getElementById('sampleanimation'); 
  var position = 0;
  var id = setInterval(frame, 10);
  function frame() {
    if (position == 350) {
      clearInterval(id);
    } else {
      position++;
      elem.style.top = position + 'px';
      elem.style.left = position + 'px';
    }

  }

}

</script>"))),   

# CSS Code
singleton(tags$head(HTML("
<style type='text/css'>

#sampleanimation {
width: 50px;
height: 50px;
position: absolute;
background-color: blue;
}

</style>"))),

# HTML Code   

box(tags$body(HTML("<p>
<button onclick='demojs()'>Click Me</button> </p>
<div id ='sampleanimation'>
</div>
")), height = 400)

))

server = function(input, output) { }

runApp(list(ui = ui, server = server), launch.browser =T)


Important Note
In JS, CSS and HTML code, you need to handle double quotes as using it under R function would mean closing the function which throws execution errors. You can treat them using any of the two methods listed below.

1. Use backslash "\" to escape double quotes like below -

  var elem = document.getElementById(\"sampleanimation\"); 

2. Replace double quotation mark with single quotation mark under shiny's HTML(" ") function.

singleton function ensures that the HTML, CSS and JS files will be included just one time. They may appear in the generating code more than once.

Method II : Call JavaScript and CSS files in Shiny

You can use includeScript( ) and includeCSS( ) functions to refer JS and CSS codes from files saved in your local directory. You can save the files anywhere and mention the file location of them in the functions.

How to create JS and CSS files manually
Open notepad and paste JS code and save it with .js file extension and file type "All files" (not text document). Similarly you can create css file using .css file extension.
library(shinydashboard)

# User Interface
ui <- 
  dashboardPage(
    dashboardHeader(title = "Basic Use of JS and CSS"),
    dashboardSidebar(),
    dashboardBody(
      
  # Call Javasript and CSS Code from file
  singleton(tags$head(
  includeScript("C:\\Users\\DELL\\Documents\\animate.js"),
  includeCSS("C:\\Users\\DELL\\Documents\\animation.css")
  )),

# HTML Code      
box(tags$body(HTML("<p>
<button onclick='demojs()'>Click Me</button> </p>
<div id ='sampleanimation'>
</div>
")), height = 400)
))

server = function(input, output) { }

runApp(list(ui = ui, server = server), launch.browser =T)


When to use Method 2?
When you want to include a big (lengthy) JS / CSS code, use method 2. Method 1 should be used for small code snippets as RStudio does not support coloring and error-checking of JS / CSS code. Also it makes code unnecessary lengthy which makes difficult to maintain.

htmltools : Add CSS and JS files
This method is generally used by package authors. They use htmlDependency( ) function of htmltools package to add CSS and JS files. This method allows you to add files from package or you can also include external static CSS and JS files.
ui <- fluidPage(
  
htmltools::htmlDependency(name = "font-awesome", 
                            version = "5.13.0", 
                            src = "www/shared/fontawesome", 
                            package = "shiny", 
                            stylesheet = c("css/all.min.css", "css/v4-shims.min.css"),
                            script = NULL),
  
htmltools::htmlDependency(
    name = "darkmodejs",
    version = "1.5.3",
    src = c(file = "", href = "https://cdn.jsdelivr.net/npm/darkmode-js@1.5.3/lib/"),
    package = "mypackage",
    script = "darkmode-js.min.js",
    all_files = FALSE)
  
)

server <- function(input, output, session) {
  
}

# Run App
shinyApp(ui = ui, server = server)
After running the above code, run app in your browser and check View Source and you will observe the following files have been included.
 
<link href="font-awesome-5.13.0/css/all.min.css" rel="stylesheet" />
<link href="font-awesome-5.13.0/css/v4-shims.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/darkmode-js@1.5.3/lib/darkmode-js.min.js"></script>


Method III : Add JS and CSS files under www directory

Step 1 : 
Create an app using shinyApp( ) function and save it as app.R. Refer the code below.
library(shiny)
library(shinydashboard)

app <- shinyApp(
ui <- dashboardPage(
    dashboardHeader(title = "Basic Use of JS"),
    dashboardSidebar(),
    dashboardBody(
      
  # Javasript and CSS Code
  singleton(tags$head(tags$script(src='animate.js'))),
  singleton(tags$head(tags$link(rel="stylesheet", type = "text/css", href = "animation.css"))),
  
  # HTML Code
  box(tags$body(HTML("<p>
<button onclick='demojs()'>Click Me</button> </p>
<div id ='sampleanimation'>
</div>
")), height = 400)
))
,
server = function(input, output) { }
)


Step 2 :
Create a folder named www in your app directory (where your app app.r file is stored) and save .js and .css files under the folder. Refer the folder structure below.
├── app.R
└── www
    └── animate.js
    └── animation.css

Step 3 :
Submit runApp( ) function. Specify path of app directory.
runApp(appDir = "C:/Users/DELL/Documents", launch.browser = T)

Method IV : Using Shinyjs R Package

The shinyjs package allows you to perform most frequently used JavaScript tasks without knowing JavaScript programming at all. For example, you can hide, show or toggle element. You can also enable or disable input.

Example : Turn content on and off by pressing the same button

Make sure to install shinyjs package before loading it. You can install it by using install.packages("shinyjs").

Important Point : Use function useShinyjs( ) under dashboardBody( ) to initialize shinyjs library
library(shiny)
library(shinydashboard)
library(shinyjs)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody(
    useShinyjs(),
    actionButton("button", "Click me"),
    div(id = "id1", "Sample Text")
  )
)

server <- function(input, output) {
  observeEvent(input$button, {
    toggle("id1")
  })
}

runApp(list(ui = ui, server = server), launch.browser =T)


In the above program, we have used toggle( ) function to turn content on and off.


Example : Enable or disable Numeric Input based on checkbox selection

library(shiny)
library(shinydashboard)
library(shinyjs)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody(
    useShinyjs(),
    numericInput("sampleinput", "Categories", 1),
    checkboxInput("id1", label="Enable Input Box")
  ) 
  )
  
  server = function(input, output, session) {
    observeEvent(input$id1, {
      if(input$id1 == F){
        disable("sampleinput")
      } else {
        enable("sampleinput")
      }
    })
  }


runApp(list(ui = ui, server = server), launch.browser =T)

Communication from R to JavaScript
Suppose you have some javascript and you want to interact it with R. Here we are using Javascript in Server( ) section of shiny App.

1. session$sendCustomMessage( ) tells R to send communication to Javascript (which is active and ready to catch message). In this example we are closing the current tab of window via JS -

  jscode <- "
  window.open('','_parent','');
  window.close();"    
session$sendCustomMessage(type = "closeWindow", list(message = jscode))
2. To tell JavaScript to receive message from R, we need to use Shiny.addCustomMessageHandler( ) in UI.
    tags$script(
      "Shiny.addCustomMessageHandler('closeWindow', function(data) {
       eval(data.message)
      });"
    )
eval( ) is used to execute javascript which is in string above. See the complete example below -
library(shiny)
library(shinydashboard)

jscode <- "
  window.open('','_parent','');
  window.close();
"

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody(
    actionButton("close", "Close app"),
    tags$script(
      "Shiny.addCustomMessageHandler('closeWindow', function(data) {
       eval(data.message)
      });"
    )
  ) 
)

server = function(input, output, session) {
  observeEvent(input$close, {
    
    session$sendCustomMessage(type = "closeWindow", list(message = jscode))
    
  })
}

runApp(list(ui = ui, server = server), launch.browser =T)
You can also define and call your own JavaScript function using shinyjs package with the use of runjs function inside server( ). Don't forget to add useShinyjs() in UI to make this work.
  
library(shiny)
library(shinydashboard)
library(shinyjs)

jscode <- "
  window.open('','_parent','');
  window.close();
"

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody(
    shinyjs::useShinyjs(),
    actionButton("close", "Close app")
  ) 
)

server = function(input, output, session) {
  observeEvent(input$close, {
    
    runjs(jscode)
    
  })
}

runApp(list(ui = ui, server = server), launch.browser =T)

You can also accomplish this via extendShinyjs( ) function which needs to be included inside dashboardBody( ).
  1. Make sure to define custom JavaScript function beginning with word shinyjs
  2. JS function should be inside quotes
  3. In server, you can call the function by writing js$function-name
The program below closes app when user clicks on action button.

library(shiny)
library(shinydashboard)
library(shinyjs)

jscode <- "shinyjs.exitWindow = function () {
  window.open('','_parent','');
  window.close();
}"

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody(
    shinyjs::useShinyjs(),
    extendShinyjs(text = jscode),
    actionButton("close", "Close app")
  ) 
  )
  
  server = function(input, output, session) {
    observeEvent(input$close, {
      js$exitWindow();
    })
  }

runApp(list(ui = ui, server = server), launch.browser =T)
Communication from JavaScript to R
Incase you are interested to send message from Javascript and pass it to R. You can do it via Shiny.onInputChange( ) In this example we are trying to capture no. of times user clicked on button. Whenever button gets clicked, it stores that in reactive value and increment it by one in each time button gets clicked further.
library(shiny)

jscode <- '
$("#clickbtn").on("click", function(){
  Shiny.onInputChange("buttonClicked", Math.random(), {priority: "event"});
})
'

ui  <- fluidPage(
  actionButton("clickbtn", "Click Me"),
  singleton(tags$script(HTML(jscode))),
  textOutput("text")
)

server  <- function(input, output){
  
  nclick  <- reactiveVal(0)
  
  observeEvent(input$buttonClicked, {
    nclick(nclick() + 1)
    
    output$text  <- renderText({ paste("No. of Clicks :", nclick()) })
  })
  
}

shinyApp(ui = ui, server = server)

Access JavaScript Variable in R
In the example below we are trying to fetch URL of the shiny app page where it is running. Shiny app itself can be embedded in any other web application so it's important to get URL of the main site where it is active. This is useful if you want to restrict your app to some URLs.
library(shinyjs)
library(shiny)

shinyApp(
  
  ui = fluidPage(
    useShinyjs(),
    textOutput("text")
  ),
  
  server = function(input, output) {
    
      # Get main URL
      runjs("var url = (window.location != window.parent.location) ? document.referrer: document.location.href; Shiny.onInputChange('my_link',url, {priority: 'event'});")
      output$text <- renderText({ input$my_link })
        
  }
  
)

Bonus
When you are writing a user-defined R function and you want to convert inputs (arguments) of the function to JSON in javascript, you can use jsonlite::toJSON( ) function for the same.
x <- list(time = '0.5s', convert = TRUE)
jsonlite::toJSON(x, auto_unbox = TRUE)
Incase you have JSON and you need to convert it to R object, you can use fromJSON function.
jsonlite::fromJSON('{"time":"0.5s","convert":true}')
End Notes
With the huge popularity of JavaScript and many recent advancements, it is recommended to learn basics of JavaScript so that you can use them in R Shiny app. According to latest survey, JavaScript is used by 95% of websites. Its huge popularity is because of active broad JS developers community and being used by big players like Google, Facebook, Microsoft, etc.
Do comment on how you use shiny app in the comment box below. If you are beginner and want to learn building webapp using shiny, check out this tutorial
Related Posts
Spread the Word!
Share
About Author:
Deepanshu Bhalla

Deepanshu founded ListenData with a simple objective - Make analytics easy to understand and follow. He has over 10 years of experience in data science. During his tenure, he worked with global clients in various domains like Banking, Insurance, Private Equity, Telecom and HR.

0 Response to "Add JavaScript and CSS in Shiny"
Next → ← Prev