Skip to content

Add includeHTMLDocument() to embed a complete document #383

@gadenbuie

Description

@gadenbuie

App and document authors frequently want to embed a complete document in the parent app or document, often generating the subdocument with R Markdown, Quarto or a stand-alone HTML widget.

An example implementation might look like this:

includeHTMLDocument <- function(path, ..., height = NULL, fill = is.null(height)) {
  stopifnot(is.character(path), length(path) == 1)

  iframe_args_default <- list(
    width = "100%",
    height = height %||% "400px",
    frameborder = "0"
  )

  iframe_args <- utils::modifyList(iframe_args_default, dots_list(...))

  if (inherits(path, "AsIs") || grepl("^http?s://", path)) {
    iframe_args$src <- as.character(path)
  } else {
    lines <- readLines(path, warn=FALSE, encoding='UTF-8')
    iframe_args$srcdoc <- paste8(lines, collapse='\n')
  }
  
  bindFillRoll(tags$iframe(class = "html-fill-item", !!!iframe_args), fill = fill)
}

There are three reasons we haven't moved forward with that, though:

  1. Using the srcdoc attribute makes the most sense when the document is rendered with self_contained = TRUE or selfcontained = TRUE, which is the default in rmarkdown::render() and htmlwidgets::saveWidget(), repsectively.

    When these aren't true, the resource paths in the subdocument are relative paths. The usage of srcdoc then implicitly makes all relative paths in the subdocument relative to the parent document, which is often not the case.

  2. The best choice for self_contained = TRUE is to render the document into subfolder that is also hosted, e.g. rendering into a folder that's automatically served with the app, like www/, or rendering into a folder that has been attached with shiny::addResourcePath().

  3. Authors would also like to use includeHTMLDocument() to embed external URLs, which would use the src attribute.

It's easy to detect case 3 by checking path for a full URL starting with https://. Case 1 and 2 are hard to distinguish, we won't know the right choice from path alone. The implementation above tries to solve this by making srcdoc (case 1) the default and asking authors to opt into case 2 by wrapping the path in I(). Another option might be to use absolute (starting with /) paths or relative paths to distinguish between cases 1 and 2.

Part of what stalled progress in #382 is that it's nice when everything works out but it's easy to fall into an ambiguous use case where it's not clear which option to choose and how to nudge the user toward the right choice.

Finally, another option worth exploring is to use an htmlDependency() to make the document and its surrounding files available. There's some prior art in posit-dev/py-shiny#127 and this path would also motivate improvements across the board to all include*() functions, namely to add a method argument and provide more options as to how content is included in the app or HTML document.

Example app for testing
library(shiny)
library(bslib)
library(leaflet)

tmp_www <- fs::dir_create(tempfile())

# A complete html document containing a leaflet map
tmp_map <- file.path(tmp_www, "map.html")
map <- leaflet() |>
  addTiles() |>
  setView(-93.65, 42.0285, zoom = 17) |>
  addPopups(-93.65, 42.0285, "Here is the <b>Department of Statistics</b>, ISU")

htmlwidgets::saveWidget(map, tmp_map, selfcontained = TRUE)

# An HTML fragment, not a complete document
tmp_html <- tempfile(fileext = ".html")
writeLines(format(as.tags(lorem::ipsum(3, 3))), tmp_html)

ui <- page_fluid(
  titlePanel("Hello embedded html in Shiny!"),
  card(
    card_header("A leaflet map, fully encapsulated"),
    full_screen = TRUE,
    includeHTMLDocument(tmp_map)
  ),
  h3("Just some more HTML"),
  includeHTML(tmp_html)
)
srv <- function(input, output) {}
shinyApp(ui, srv)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions