Nothing
#' Epoxy HTML Output for Shiny
#'
#' A glue-like output for Shiny. `ui_epoxy_html()` lets you use placeholders in
#' your HTML such as `"{{first_name}}"`, that are provided values from the
#' server by giving `render_epoxy()` a `first_name` value. Unlike
#' [ui_epoxy_mustache()], updates are highly targeted: only the regions where
#' the server-side data have changed are updated in `ui_epoxy_html()`.
#'
#' @section HTML Markup: By default, placeholders are inserted into a `<span>`
#' element in your UI, with the classes specified in `.class_item`.
#'
#' `ui_epoxy_html()` also supports an HTML markup syntax similar to
#' [pug](https://pughtml.com/what-is-pug-html) (an HTML preprocessor). As an
#' example, the markup syntax
#' ```
#' "{{h3.example.basic#basic-three demo}}"
#' ```
#' creates a `demo` placeholder inside the following tag.
#' ```
#' <h3 id="basic-three" class="example basic"></h3>
#' ```
#'
#' The placeholder template string follows the pattern `{{<markup> <name>}}`.
#' The markup syntax comes first, separated from the placeholder name by a
#' space. The HTML element is first, followed by classes prefixed with `.` or
#' and ID prefixed with `#`. The template markup can contain only one element
#' and one ID, but many classes can be specified.
#'
#' By default, the placeholder is assumed to be text content and any HTML
#' in the sent to the placeholder will be escaped --- in other words if you sent
#' `"<strong>word</strong>"`, you'd see that exact literal text in your app,
#' rather than an emboldened **word**. To mark a placeholder as safe to accept
#' HTML, use `!!` before the placeholder, e.g. `{{<markup> !!<name>}}`. So
#' `{{h3 !!demo}}` will create an `<h3>` tag that accepts HTML within it.
#'
#' @examplesIf rlang::is_installed("shiny")
#' library(shiny)
#'
#' ui <- fluidPage(
#' h2("ui_epoxy_html demo"),
#' ui_epoxy_html(
#' .id = "example",
#' .item_class = "inner",
#' fluidRow(
#' tags$div(
#' class = "col-xs-4",
#' selectInput(
#' inputId = "thing",
#' label = "What is this {{color}} thing?",
#' choices = c("apple", "banana", "coconut", "dolphin")
#' )
#' ),
#' tags$div(
#' class = "col-xs-4",
#' selectInput(
#' inputId = "color",
#' label = "What color is the {{thing}}?",
#' c("red", "blue", "black", "green", "yellow")
#' )
#' ),
#' tags$div(
#' class = "col-xs-4",
#' sliderInput(
#' inputId = "height",
#' label = "How tall is the {{color}} {{thing}}?",
#' value = 5,
#' min = 0,
#' max = 10,
#' step = 0.1,
#' post = "ft"
#' )
#' )
#' ),
#' tags$p(class = "big", "The {{color}} {{thing}} is {{height}} feet tall."),
#' # Default values for placeholders above.
#' thing = "THING",
#' color = "COLOR",
#' height = "HEIGHT"
#' ),
#' tags$style(HTML(
#' ".big { font-size: 1.5em; }
#' .inner { background-color: rgba(254, 233, 105, 0.5);}
#' .epoxy-item__placeholder { color: #999999; background-color: unset; }"
#' ))
#' )
#'
#' server <- function(input, output, session) {
#' output$example <- render_epoxy(
#' thing = input$thing,
#' color = input$color,
#' height = input$height
#' )
#' }
#'
#' if (interactive()) {
#' shinyApp(ui, server)
#' }
#'
#' @eval write_epoxy_example_app("ui_epoxy_html")
#'
#' @param .id The output id
#' @param ... UI elements or text (that will be treated as HTML), containing
#' template variables. Use named values to provide initial placeholder values.
#' @param .class,.style Classes and inline style directives added to the
#' `<epoxy-html>` container into which the elements in `...` are placed.
#' @param .item_tag,.item_class The HTML element tag name and classes used to
#' wrap each template variable. By default, each template is wrapped in a
#' `<span>`.
#' @param .placeholder Default placeholder if a template variable placeholder
#' isn't provided.
#' @param .aria_live,.aria_atomic The
#' [aria-live](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-live)
#' and [aria-atomic](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-atomic)
#' attribute values for the entire template region. By default, with
#' `"polite"`, any updates within the region will be announced via screen
#' readers.
#'
#' If your template includes changes in lots of disparate areas, it would be
#' better to set `"aria-live" = "polite"` and `"aria-atomic" = "true"` on
#' specific regions that should be announced together. Otherwise, the default
#' is to announce the entire region within the `ui_epoxy_html()` whenever any
#' of the values within change. In other words, set `.aria_live = "off"` and
#' `.aria_atomic = NULL` on the `ui_epoxy_html()` parent item and then set
#' `"aria-live" = "polite"` and `"aria-atomic" = "true"` on the parent
#' containers of each region in the app that receives updates.
#' `ui_epoxy_html()` does targeted updates, changing only the parts of the
#' UI that have changed.
#' @inheritParams epoxy
#' @inheritParams glue::glue
#' @param .container `r lifecycle::badge("deprecated")` Deprecated in
#' \pkg{epoxy} v1.0.0, where the container is now always `<epoxy-html>`.
#' @param .class_item `r lifecycle::badge("deprecated")` Deprecated in
#' \pkg{epoxy} v1.0.0, please use `.item_class` instead.
#' @param .container_item `r lifecycle::badge("deprecated")` Deprecated in
#' \pkg{epoxy} v1.0.0, please use `.item_tag` instead.
#'
#' @seealso [ui_epoxy_mustache()], [render_epoxy()]
#' @return An HTML object.
#' @export
ui_epoxy_html <- function(
.id,
...,
.class = NULL,
.style = NULL,
.item_tag = "span",
.item_class = NULL,
.placeholder = "",
.sep = "",
.open = "{{",
.close = "}}",
.na = "",
.null = "",
.literal = FALSE,
.trim = FALSE,
.aria_live = c("polite", "off", "assertive"),
.aria_atomic = TRUE,
# Deprecated arguments ----
.class_item = deprecated(),
.container = deprecated(),
.container_item = deprecated()
) {
if (lifecycle::is_present(.container)) {
lifecycle::deprecate_warn(
"1.0.0",
"ui_epoxy_html(.container = )",
details = "This argument is no longer used."
)
}
if (lifecycle::is_present(.container_item)) {
lifecycle::deprecate_warn(
"1.0.0",
"ui_epoxy_html(.container_item = )",
"ui_epoxy_html(.item_container = )"
)
if (missing(.item_tag)) {
.item_tag <- .container_item
}
}
if (lifecycle::is_present(.class_item)) {
lifecycle::deprecate_warn(
"1.0.0",
"ui_epoxy_html(.class_item = )",
"ui_epoxy_html(.item_class = )"
)
if (missing(.item_class)) {
.item_class <- .class_item
}
}
.item_container <- match.arg(.item_tag, names(htmltools::tags))
.aria_live <- rlang::arg_match(.aria_live)
.aria_atomic <- if (!is.null(.aria_atomic)) {
if (isTRUE(.aria_atomic)) "true" else "false"
}
dots <- rlang::list2(...)
dots$.placeholder <- .placeholder
dots$.transformer <- epoxyHTML_transformer(.item_class, .item_tag)
dots$.na <- .na
dots$.sep <- .sep
dots$.null <- .null
dots$.trim <- .trim
dots$.open <- .open %||% "{{"
dots$.close <- .close %||% "}}"
# disable # as comment so we can use it for id syntax (requires glue >= 1.5)
dots$.comment <- character()
dots$.literal <- .literal
dots$.envir <- new.env(parent = emptyenv())
tags <- purrr::keep(dots, is_tag)
deps <- if (length(tags)) {
purrr::flatten(purrr::map(tags, htmltools::findDependencies))
}
dots <- purrr::map_if(dots, ~ inherits(.x, "shiny.tag"), format)
res <- rlang::eval_bare(rlang::call2(glue::glue, !!!dots))
out <- htmltools::tag(
"epoxy-html",
list(
id = .id,
class = "epoxy-html epoxy-init",
class = .class,
style = .style,
"aria-atomic" = .aria_atomic,
"aria-live" = .aria_live,
htmltools::HTML(res),
html_dependency_epoxy(),
html_dependency_hint_css()
)
)
if (!is.null(deps) && length(deps)) {
htmltools::attachDependencies(out, deps)
} else {
out
}
}
html_dependency_epoxy <- function() {
htmltools::htmlDependency(
name = "epoxy",
version = "0.0.1",
package = "epoxy",
src = "srcjs",
script = "output-epoxy.js",
stylesheet = "output-epoxy.css",
all_files = FALSE
)
}
html_dependency_hint_css <- function() {
htmltools::htmlDependency(
name = "hint.css",
version = "2.7.0",
package = "epoxy",
src = "lib/hint.css",
stylesheet = "hint.min.css",
all_files = FALSE
)
}
#' Epoxy Markdown Template for Shiny
#'
#' Create reactive HTML from a Markdown template. `ui_epoxy_markdown()` uses the
#' same template syntax as [ui_epoxy_html()], but rather than requiring HTML
#' inputs, you can write in markdown. The template is first rendered from
#' markdown to HTML using [pandoc::pandoc_convert()] (if \pkg{pandoc} is
#' available) or [commonmark::markdown_html()] otherwise.
#'
#' @param ... Unnamed arguments are treated as lines of markdown text, and named
#' arguments are treated as initial values for templated variables.
#' @param .markdown_fn The function used to convert the markdown to HTML. This
#' function is passed the markdown text as a character vector for the first
#' argument and any additional arguments from the list `.markdown_args`. By
#' default, we use [pandoc::pandoc_convert()] if \pkg{pandoc} is available,
#' otherwise we use [commonmark::markdown_html()].
#' @param .markdown_args A list of arguments to pass to
#' [commonmark::markdown_html()].
#' @inheritParams ui_epoxy_html
#'
#' @examplesIf rlang::is_installed("shiny") && rlang::is_interactive()
#' library(shiny)
#'
#' # Shiny epoxy template functions don't support inline transformations,
#' # so we still have to do some prep work ourselves.
#' bechdel <- epoxy::bechdel
#'
#' as_dollars <- scales::label_dollar(
#' scale_cut = scales::cut_short_scale()
#' )
#' bechdel$budget <- as_dollars(bechdel$budget)
#' bechdel$domgross <- as_dollars(bechdel$domgross)
#'
#' vowels <- c("a", "e", "i", "o", "u")
#' bechdel$genre <- paste(
#' ifelse(substr(tolower(bechdel$genre), 1, 1) %in% vowels, "an", "a"),
#' tolower(bechdel$genre)
#' )
#'
#' movie_ids <- rlang::set_names(
#' bechdel$imdb_id,
#' bechdel$title
#' )
#'
#' ui <- fixedPage(
#' fluidRow(
#' column(
#' width = 3,
#' selectInput("movie", "Movie", movie_ids),
#' uiOutput("poster")
#' ),
#' column(
#' width = 9,
#' ui_epoxy_markdown(
#' .id = "about_movie",
#' "
#' ## {{title}}
#'
#' **Released:** {{ year }} \\
#' **Rated:** {{ rated }} \\
#' **IMDB Rating:** {{ imdb_rating }}
#'
#' _{{ title }}_ is {{ genre }} film released in {{ year }}.
#' It was filmed in {{ country }} with a budget of {{ budget }}
#' and made {{ domgross }} at the box office.
#' _{{ title }}_ recieved a Bechdel rating of **{{ bechdel_rating }}**
#' for the following plot:
#'
#' > {{ plot }}
#' "
#' )
#' )
#' )
#' )
#'
#' server <- function(input, output, session) {
#' movie <- reactive({
#' bechdel[bechdel$imdb_id == input$movie, ]
#' })
#'
#' output$about_movie <- render_epoxy(.list = movie())
#' output$poster <- renderUI(
#' img(
#' src = movie()$poster,
#' alt = paste0("Poster for ", movie()$title),
#' style = "max-height: 400px; max-width: 100%; margin: 0 auto; display: block;"
#' )
#' )
#' }
#'
#' if (interactive()) {
#' shinyApp(ui, server)
#' }
#'
#' @eval write_epoxy_example_app("ui_epoxy_markdown")
#'
#' @seealso [ui_epoxy_html()], [ui_epoxy_mustache()], [render_epoxy()]
#' @return An HTML object.
#' @export
ui_epoxy_markdown <- function(
.id,
...,
.markdown_fn = NULL,
.markdown_args = list(),
.class = NULL,
.style = NULL,
.item_tag = "span",
.item_class = NULL,
.placeholder = "",
.sep = "",
.open = "{{",
.close = "}}",
.na = "",
.null = "",
.literal = FALSE,
.trim = FALSE,
.aria_live = c("polite", "off", "assertive"),
.aria_atomic = TRUE,
# Deprecated arguments ----
.class_item = deprecated(),
.container = deprecated(),
.container_item = deprecated()
) {
dots <- list_split_named(rlang::dots_list(...))
lines <- dots[["unnamed"]]
dots <- dots[["named"]]
if (is.null(lines)) {
rlang::abort(
"You must provide at least one line of markdown text in `...` as an unnamed character string or vector."
)
}
if (is.null(.markdown_fn)) {
.markdown_fn <- function(lines, ...) {
if (rlang::is_installed("pandoc")) {
x <- pandoc::pandoc_convert(text = lines, to = "html", ...)
return(paste(x, collapse = "\n"))
}
rlang::check_installed("commonmark", "for converting markdown to HTML")
commonmark::markdown_html(lines, ...)
}
}
html <- rlang::exec(.markdown_fn, lines, !!!.markdown_args)
ui_epoxy_html(
.id,
htmltools::HTML(html),
!!!dots,
.class = .class,
.style = .style,
.item_tag = .item_tag,
.item_class = .item_class,
.placeholder = .placeholder,
.sep = .sep,
.open = .open,
.close = .close,
.na = .na,
.null = .null,
.literal = .literal,
.trim = .trim,
.aria_live = .aria_live,
.aria_atomic = .aria_atomic,
# Deprecated arguments ----
.class_item = .class_item,
.container = .container,
.container_item = .container_item
)
}
#' @describeIn ui_epoxy_html `r lifecycle::badge('deprecated')` Deprecated
#' alias, please use `ui_epoxy_html()`.
#' @export
epoxyHTML <- function(.id, ...) {
lifecycle::deprecate_warn(
"0.1.0",
"epoxyHTML()",
"ui_epoxy_html()",
details = "`epoxyHTML()` was renamed. Please use the new name at your earliest convenience."
)
ui_epoxy_html(.id, ...)
}
transformer_js_literal <- function(text, envir) {
paste0("${", text, "}")
}
epoxyHTML_transformer <- function(
class = NULL,
element = "span"
) {
function(text, envir) {
'!DEBUG epoxyHTML {text: "`text`"}'
markup <- parse_html_markup(text)
placeholder <- rlang::env_get(
markup$item,
env = envir,
inherit = TRUE,
default = get0(".placeholder", envir, inherits = FALSE)
)
if (!is.null(placeholder)) {
placeholder <- htmltools::HTML(placeholder)
}
tag_name <- markup$element
if (is.null(tag_name)) tag_name <- element
htmltools::tag(
tag_name,
list(
class = "epoxy-item__placeholder",
class = class,
class = markup$class,
id = markup$id,
`data-epoxy-item` = markup$item,
`data-epoxy-as-html` = tolower(markup$as_html %||% FALSE),
`data-epoxy-placeholder` = placeholder,
placeholder
)
)
}
}
#' Epoxy HTML Mustache Template
#'
#' A Shiny output that uses [mustache templating](https://mustache.github.io/)
#' to render HTML. Mustache is a powerful template language with minimal
#' internal logic. The advantage of `ui_epoxy_mustache()` is that all parts of
#' the HTML can be templated -- including element attributes -- whereas
#' [ui_epoxy_html()] requires that the dynamic template variables appear in the
#' text portion of the UI. The downside is that the entire template is
#' re-rendered (in the browser), each time that updated data is sent from the
#' server -- unlike [ui_epoxy_html()], whose updates are specific to the parts
#' of the data that have changed.
#'
#' @examplesIf rlang::is_installed("shiny")
#' library(shiny)
#'
#' ui <- fluidPage(
#' fluidRow(
#' style = "max-width: 600px; margin: 0 auto",
#' column(
#' width = 6,
#' ui_epoxy_mustache(
#' id = "template",
#' h2(class = "{{heading_class}}", "Hello, {{name}}!"),
#' "{{#favorites}}",
#' p("Your favorite fruits are..."),
#' tags$ul(HTML("{{#fruits}}<li>{{.}}</li>{{/fruits}}")),
#' "{{/favorites}}",
#' "{{^favorites}}<p>Do you have any favorite fruits?</p>{{/favorites}}"
#' )
#' ),
#' column(
#' width = 6,
#' h2("Inputs"),
#' textInput("name", "Your name"),
#' textInput("fruits", "Favorite fruits", placeholder = "apple, banana"),
#' helpText("Enter a comma-separated list of fruits.")
#' )
#' )
#' )
#'
#' server <- function(input, output, session) {
#' user_name <- reactive({
#' if (!nzchar(input$name)) return("user")
#' input$name
#' })
#'
#' favorites <- reactive({
#' if (identical(input$fruits, "123456")) {
#' # Errors are equivalent to "empty" values,
#' # the rest of the template will still render.
#' stop("Bad fruits, bad!")
#' }
#'
#' if (!nzchar(input$fruits)) return(NULL)
#' list(fruits = strsplit(input$fruits, "\\s*,\\s*")[[1]])
#' })
#'
#' output$template <- render_epoxy(
#' name = user_name(),
#' heading_class = if (user_name() != "user") "text-success",
#' favorites = favorites()
#' )
#' }
#'
#' if (interactive()) {
#' shiny::shinyApp(ui, server)
#' }
#'
#' @eval write_epoxy_example_app("ui_epoxy_mustache")
#'
#' @param id The ID of the output.
#' @param ... Character strings of HTML or [htmltools::tags]. All elements
#' should be unnamed.
#' @param .file A path to a template file. If provided, no other template lines
#' should be included in `...`.
#' @param .sep The separator used to concatenate elements in `...`.
#' @param .container A character tag name, e.g. `"div"` or `"span"`, or a
#' function that returns an [htmltools::tag()].
#'
#' @return Returns a Shiny output UI element.
#'
#' @family Mustache-style template functions
#' @seealso [ui_epoxy_html()], [render_epoxy()]
#' @export
ui_epoxy_mustache <- function(
id,
...,
.file = NULL,
.sep = "",
.container = "epoxy-mustache"
) {
rlang::check_dots_unnamed()
if (is.character(.container)) {
tag_name <- .container
.container <- function(...) {
htmltools::tag(tag_name, list(..., "aria-live" = "polite"))
}
}
dots <- rlang::list2(...)
if (length(dots) == 0) {
if (is.null(.file)) return(NULL)
dots <- as.list(readLines(.file))
} else {
if (!is.null(.file)) {
rlang::abort("Cannot specify both `...` and `.file`.")
}
}
tags <- purrr::keep(dots, is_tag)
deps <- purrr::flatten(purrr::map(tags, htmltools::findDependencies))
dots <- purrr::map_if(dots, is_tag, format)
dots <- purrr::flatten_chr(dots)
if (!purrr::every(dots, is.character)) {
rlang::abort("All template elements in `...` must be characters or htmltools tags.")
}
out <- .container(
id = id,
class = "epoxy-mustache",
`data-epoxy-template` = paste(dots, collapse = .sep),
epoxy_mustache_dependencies()
)
if (!is.null(deps) && length(deps)) {
htmltools::attachDependencies(out, deps)
} else {
out
}
}
#' @describeIn ui_epoxy_mustache An alias for `ui_epoxy_mustache()`, provided
#' because R users are more familiar with this syntax via the \pkg{whisker}
#' package.
#' @export
ui_epoxy_whisker <- ui_epoxy_mustache
epoxy_mustache_dependencies <- function() {
htmltools::tagList(
htmltools::htmlDependency(
name = "mustache",
package = "epoxy",
version = "4.2.0",
src = "lib/mustache",
script = "mustache.min.js",
all_files = FALSE
),
htmltools::htmlDependency(
name = "epoxy-mustache",
package = "epoxy",
version = "0.0.1",
src = "srcjs",
script = "output-epoxy-mustache.js",
all_files = FALSE
)
)
}
#' Render Epoxy Output
#'
#' Server-side render function used to provide values for template items. Use
#' named values matching the template variable names in the associated
#' [ui_epoxy_html()] or [ui_epoxy_mustache()]. When the values are updated by
#' the app, `render_epoxy()` will update the values shown in the app's UI.
#'
#' @examplesIf rlang::is_installed("shiny")
#' # This small app shows the current time using `ui_epoxy_html()`
#' # to provide the HTML template and `render_epoxy()` to
#' # update the current time every second.
#'
#' ui <- shiny::fluidPage(
#' shiny::h2("Current Time"),
#' ui_epoxy_html(
#' "time",
#' shiny::p("The current time is {{strong time}}.")
#' )
#' )
#'
#' server <- function(input, output, session) {
#' current_time <- shiny::reactive({
#' shiny::invalidateLater(1000)
#' strftime(Sys.time(), "%F %T")
#' })
#'
#' output$time <- render_epoxy(time = current_time())
#' }
#'
#' if (rlang::is_interactive()) {
#' shiny::shinyApp(ui, server)
#' }
#'
#' @eval write_epoxy_example_app("render_epoxy")
#'
#' @param ... Named values corresponding to the template variables created with
#' the associated [ui_epoxy_html()] UI element.
#' @param .list A named list or a [shiny::reactiveValues()] list with names
#' corresponding to the template variables created with the associated
#' [ui_epoxy_html()] UI element.
#' @param env The environment in which to evaluate the `...`
#' @param outputArgs A list of arguments to be passed through to the implicit
#' call to [ui_epoxy_html()] when `render_epoxy` is used in an interactive R
#' Markdown document.
#' @param outputFunc Either [ui_epoxy_html()] or [ui_epoxy_mustache()], i.e. the
#' UI function to be paired with this output. This is only used when calling
#' `render_epoxy()` in an Shiny runtime R Markdown document and when you
#' are only providing the output without an explicit, corresponding UI
#' element.
#'
#' @return A server-side Shiny render function that should be assigned to
#' Shiny's `output` object and named to match the `.id` of the corresponding
#' [ui_epoxy_html()] call.
#'
#' @seealso [ui_epoxy_html()], [ui_epoxy_mustache()]
#' @export
render_epoxy <- function(
...,
.list = NULL,
env = parent.frame(),
outputFunc = ui_epoxy_html,
outputArgs = list()
) {
rlang::check_installed("shiny")
epoxyPrepare <- function(..., .list = NULL) {
if (!is.null(.list)) {
if (inherits(.list, "reactivevalues")) {
.list <- shiny::reactiveValuesToList(.list)
}
if (!is.list(.list)) {
stop("`.list` must be a list", call. = FALSE)
}
if (is.null(names(.list))) {
stop("`.list` must be a named list", call. = FALSE)
}
}
dots <- rlang::enquos(...)
dots <- purrr::map(dots, function(x) {
tryCatch(rlang::eval_tidy(x), error = identity)
})
errored <- c()
for (i in seq_along(dots)) {
if (rlang::cnd_inherits(dots[[i]], "error")) {
errored <- c(errored, names(dots)[i])
dots[[i]] <- conditionMessage(dots[[i]])
}
}
data <- lapply(c(dots, .list), format_tags)
if (length(errored)) {
data[["__errors__"]] <- I(errored)
}
data
}
shiny::installExprFunction(
name = "epoxyPrepare",
quoted = FALSE,
expr = epoxyPrepare(..., .list = .list)
)
shiny::createRenderFunction(
func = epoxyPrepare,
transform = function(value, session, name, ...) {
value <- as.list(value)
stopifnot(!is.null(names(value)))
value
},
outputFunc = outputFunc,
outputArgs = outputArgs
)
}
#' @describeIn render_epoxy `r lifecycle::badge('deprecated')` Deprecated alias,
#' please use `render_epoxy()`.
#' @export
renderEpoxyHTML <- function(..., env = parent.frame()) {
lifecycle::deprecate_soft("0.1.0", "renderEpoxyHTML()", "render_epoxy()")
render_epoxy(..., env = env)
}
format_tags <- function(x) {
if (!inherits(x, c("shiny.tag", "shiny.tag.list"))) {
return(x)
}
format(x)
}
# nocov start
write_epoxy_example_app <- function(name, fn_name = paste0(name, "()")) {
rd_path <- paste0(file.path("man", name), ".Rd")
ex_path <- file.path("inst", "examples", name, "app.R")
dir.create(dirname(ex_path), showWarnings = FALSE, recursive = TRUE)
tools::Rd2ex(rd_path, out = ex_path)
ex <- readLines(ex_path, warn = FALSE)
idx_start <- min(grep("## End(Don't show)", ex, fixed = TRUE))
idx_end <- max(grep("shinyApp", ex, fixed = TRUE))
if (is.infinite(idx_end)) return("")
if (nzchar(ex[idx_start])) idx_start <- idx_start + 1
if (ex[idx_start] == "library(shiny)") idx_start <- idx_start + 1
app_lines <- c(
glue("# Generated from example in {fn_name}: do not edit by hand"),
"library(shiny)",
"library(epoxy)",
"",
ex[idx_start:(idx_end - 3) + 1],
trimws(ex[idx_end])
)
writeLines(app_lines, ex_path)
c(
"\n",
"@examplesIf rlang::is_interactive()",
sprintf("run_epoxy_example_app(\"%s\")", name),
""
)
}
#' Example epoxy Shiny apps
#'
#' Run an example epoxy Shiny app showcasing the Shiny UI and server components
#' provided by epoxy.
#'
#' @examples
#' # List examples by passing `name = NULL`
#' run_epoxy_example_app(name = NULL)
#'
#' @param name Name of the example, currently one of `"ui_epoxy_html"`,
#' `"ui_epoxy_markdown"`, `"ui_epoxy_mustache"`, or `"render_epoxy"`.
#' @inheritParams shiny::runApp
#' @inheritDotParams shiny::runApp -display.mode
#'
#' @return Runs the Shiny example app interactively. Nothing is returned.
#'
#' @seealso [ui_epoxy_html()], [ui_epoxy_markdown()], [ui_epoxy_mustache()], [render_epoxy()]
#' @export
run_epoxy_example_app <- function(
name = c("ui_epoxy_html", "ui_epoxy_markdown", "ui_epoxy_mustache", "render_epoxy"),
display.mode = "showcase",
...
) {
rlang::check_installed("shiny")
apps <- list.dirs(
system.file("examples", package = "epoxy"),
recursive = FALSE
)
names(apps) <- basename(apps)
if (is.null(name)) {
rlang::inform(c("Example app options:", names(apps)))
return(invisible(apps))
}
name <- rlang::arg_match(name, names(apps))
if (identical(Sys.getenv("TESTTHAT"), "true")) {
return(apps[[name]])
}
shiny::runApp(apps[[name]], display.mode = display.mode, ...)
}
# nocov end
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.