Shiny Inception: JavaScript in Rendered Markdown

I’m busy helping a colleague with a Shiny application. The application includes HTML content rendered from a .Rmd document. However, there’s a catch: the .Rmd uses the {DT} package to render a dynamic DataTable. It turns out that this doesn’t immediately work because the JavaScript in the embedded document isn’t run.

I’ll use a simple document and application structure to illustrate the problem.

Static Document

Let’s start with a .Rmd document which renders two different static views of the Palmer Archipelago (Antarctica) Penguin Data.

This is what the rendered document looks like:

A Shiny app with static views of the Penguins data.

The following Shiny application embeds this document perfectly using includeHTML() to read in the HTML file and htmlOutput() to inject it into the UI.

library(shiny)
library(rmarkdown)

ui <- fluidPage(
    htmlOutput("document")
)

server <- function(input, output) {
    output$document <- renderUI({
        path_rmd <- "penguins-static.Rmd"
        path_html <- tempfile(fileext = ".html")
        render(
            path_rmd,
            output_file = path_html
        )
        includeHTML(path_html)
    })
}

shinyApp(ui = ui, server = server)

And this is what the application looks like. Precisely as expected.

Shiny app with embedded static HTML document.

Dynamic Document

Now, let’s replace the static document with a .Rmd document using the {DT} package to create a dynamic table. Simply knitting the document gives the output below, a responsive table with a selection of bells and whistles.

Dynamic HTML document rendered from R Markdown and using the {DT} package.

How about simply plugging this into the original Shiny application?

library(shiny)
library(rmarkdown)

ui <- fluidPage(
    htmlOutput("document")
)

server <- function(input, output) {
    output$document <- renderUI({
        path_rmd <- "penguins-dt.Rmd"
        path_html <- tempfile(fileext = ".html")
        render(
            path_rmd,
            output_file = path_html
        )
        includeHTML(path_html)
    })
}

shinyApp(ui = ui, server = server)

Let’s give that an optimistic whirl.

Result of embedding dynamic document into Shiny app: it doesn't work!

Doh! The table doesn’t appear at all. Why? I’m guessing that the viewer is using JavaScript to run the application but it doesn’t extend to running JavaScript in an embedded document.

To get this to work I used an <iframe>, which in retrospect is the obvious solution for embedding a complete HTML page into the application. Getting it to work though is not entirely trivial.

library(shiny)
library(here)
library(rmarkdown)

dir.create("www")

ui <- fluidPage(
    htmlOutput("document")
)

server <- function(input, output) {
    output$document <- renderUI({
        path_rmd <- "penguins-dt.Rmd"
        # Render into www/ folder.
        path_html <- tempfile(fileext = ".html", tmpdir = "www")
        render(
            path_rmd,
            output_file = path_html
        )
        tags$iframe(
            style = "border-width: 0;",
            width = "100%",
            height = 800,
            # Filename relative to the www/ folder.
            src = basename(path_html)
        )
    })
}

shinyApp(ui = ui, server = server)

There’s a bit of plumbing required: the document must be rendered into the www/ folder so that it’s visible to the application at runtime.

Here’s the result:

Successfully embedding dynamic HTML document into a Shiny app by putting it into the www folder.

🚀 Success!