Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inject CSS styling into RMD parametrization app #866

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
90 changes: 72 additions & 18 deletions R/params.R
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,56 @@ params_namedList <- function() {
empty
}


params_html_head <- function(html_head_style = c(),
html_head_script = c(),
html_head_style_link = c(),
html_head_script_link = c()) {

default_style <- shiny::tags$style(
# Our controls are wiiiiide.
".container-fluid .shiny-input-container { width: auto; }",
# Prevent the save/cancel buttons from squashing together.
".navbar button { margin-left: 10px; }",
# Style for the navbar footer.
# http://getbootstrap.com/components/#navbar-fixed-bottom
"body { padding-bottom: 70px; }"
)
## Escape is "cancel" and Enter is "save".
default_script <- shiny::tags$script(shiny::HTML("$(document).keyup(function(e) {\n",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any situation where we will want to not inject the defaults?

"if (e.which == 13) { $('#save').click(); } // enter\n",
"if (e.which == 27) { $('#cancel').click(); } // esc\n",
"});"
))

html_head_style <- as.vector(html_head_style)
custom_styles <- lapply(html_head_style, shiny::tags$style)

html_head_script <- as.vector(html_head_script)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the as.vector conversion? Am I missing some subtlety?

a <- c(1,2)
lapply(a, function(b) b*2)
lapply(as.vector(a), function(b) b*2)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, sorry, when I replied in the email, it put the comments in the wrong place.

the params can be strings or vector of strings.

custom_scripts <- lapply(html_head_script, function(x) shiny::tags$script(shiny::HTML(x)))

html_head_style_link <- as.vector(html_head_style_link)
custom_style_links <- lapply(html_head_style_link, function(x) shiny::tags$link(href = x))

html_head_script_link <- as.vector(html_head_script_link)
custom_script_links <- lapply(html_head_script_link, function(x) shiny::tags$script(src = x))

return (do.call(
shiny::tags$head,
append(
list(default_style, default_script),
c(
custom_style_links,
custom_styles,
custom_script_links,
custom_scripts
)
)
))
# default_style, custom_styles[0], default_script))
}


#' Run a shiny application asking for parameter configuration for the given document.
#'
#' @param file Path to the R Markdown document with configurable parameters.
Expand All @@ -193,6 +243,13 @@ params_namedList <- function() {
#' @param shiny_args Additional arguments to \code{\link[shiny:runApp]{runApp}}.
#' @param save_caption Caption to use use for button that saves/confirms parameters.
#' @param encoding The encoding of the input file; see \code{\link{file}}.
#' @param html_head_style a string or a list/vector of strings representing CSS style that will be injected in the HTML HEAD.
#' @param html_head_script a string or a list/vector of strings representing JS scripts that will be injected in the HTML HEAD.
#' @param html_head_style_link same as above except that these are interpreted as HREF attributes in LINK tags.
#' You must take care to unsure that the URL is absolute.
#' @param html_head_script_link same as above except that these are interpreted as SRC attributes in SCRIPT tags.
#' You must take care to unsure that the URL is absolute.
#' @param disable_bootstrap if true, does not inject boostrap scripts and styles in the HTML HEAD.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what the common formatting is for this package, but you could consider styling with if \code{TRUE}, does not... Probably more important to align with the conventions of the package but just making sure you're aware of the option.

#'
#' @return named list with overridden parameter names and value.
#'
Expand All @@ -202,7 +259,12 @@ knit_params_ask <- function(file = NULL,
params = NULL,
shiny_args = NULL,
save_caption = "Save",
encoding = getOption("encoding")) {
encoding = getOption("encoding"),
html_head_style = c(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One option that won't pollute the argument list so much is to have a single argument that accepts a named list.

html_head = list()

Then, if you want to specify some parts:

html_head = list(style = "body { font-size: 15px; }")

Not sure which would be preferred.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, when I replied in the email, it put the comments in the wrong place.
I initially had the same idea, but as per @trestletech , the long form is preferred.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could indeed go either way. I agree with @trestletech that R tends to stylistically prefer long argument lists. Even though this isn't normally good form in more well decomposed systems in R where many operations tend to be top level "one button" ones it makes some sense (also forces documentation of the parameters in .Rd as a side benefit).

html_head_script = c(),
html_head_style_link = c(),
html_head_script_link = c(),
disable_bootstrap = FALSE) {

if (is.null(input_lines)) {
if (is.null(file)) {
Expand Down Expand Up @@ -402,23 +464,15 @@ knit_params_ask <- function(file = NULL,
class = "container-fluid"),
class = "navbar navbar-default navbar-fixed-bottom")

style <- shiny::tags$style(
# Our controls are wiiiiide.
".container-fluid .shiny-input-container { width: auto; }",
# Prevent the save/cancel buttons from squashing together.
".navbar button { margin-left: 10px; }",
# Style for the navbar footer.
# http://getbootstrap.com/components/#navbar-fixed-bottom
"body { padding-bottom: 70px; }"
)
## Escape is "cancel" and Enter is "save".
script <- shiny::tags$script(shiny::HTML("$(document).keyup(function(e) {\n",
"if (e.which == 13) { $('#save').click(); } // enter\n",
"if (e.which == 27) { $('#cancel').click(); } // esc\n",
"});"
))
ui <- shiny::bootstrapPage(
shiny::tags$head(style, script),
buildPage <- ifelse((is.null(disable_bootstrap) | !isTruthy(disable_bootstrap)), shiny::bootstrapPage, shiny::tagList)

ui <- buildPage(
params_html_head(
html_head_style = html_head_style,
html_head_script = html_head_script,
html_head_style_link = html_head_style_link,
html_head_script_link = html_head_script_link
),
contents,
footer)

Expand Down
16 changes: 15 additions & 1 deletion man/knit_params_ask.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions tests/testthat/test-params.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
context("params_html_head")

anyHeadParams <- list(
html_head_style="style",
html_head_style_link = "link",
html_head_script = "script",
html_head_script_link = "scriptjs"
)

test_that("adds default style and script", {
result <- params_html_head()$children
expect_equal(length(result), 2)
expect_equal(result[[1]]$name, "style")
expect_equal(result[[2]]$name, "script")
})

test_that("adds custom style after default style", {
result <- do.call(params_html_head, anyHeadParams)$children
expect_equal(length(result), 6)
expect_equal(result[[4]], shiny::tags$style("style"))
})

test_that("adds custom style links after default but before inline custom styles", {
result <- do.call(params_html_head, anyHeadParams)$children
expect_equal(length(result), 6)
expect_equal(result[[3]], shiny::tags$link(href = "link"))
expect_equal(result[[4]], shiny::tags$style("style"))
})

test_that("adds custom script links after default but before inline custom scripts", {
result <- do.call(params_html_head, anyHeadParams)$children
expect_equal(length(result), 6)
expect_equal(result[[5]], shiny::tags$script(src = "scriptjs"))
expect_equal(result[[6]], shiny::tags$script(shiny::HTML("script")))
})

test_that("can pass string as style link", {
result <- params_html_head(html_head_style_link="link")$children
expect_equal(result[[3]], shiny::tags$link(href = "link"))
})

test_that("can pass vector of strings as style link", {
result <- params_html_head(html_head_style_link=c("link"))$children
expect_equal(result[[3]], shiny::tags$link(href = "link"))
})

test_that("can pass string as style", {
result <- params_html_head(html_head_style="style")$children
expect_equal(result[[3]], shiny::tags$style("style"))
})

test_that("can pass vector of strings as style", {
result <- params_html_head(html_head_style=c("style"))$children
expect_equal(result[[3]], shiny::tags$style("style"))
})

test_that("can pass string as script", {
result <- params_html_head(html_head_script="script")$children
expect_equal(result[[3]], shiny::tags$script(shiny::HTML("script")))
})

test_that("can pass vector of strings as script", {
result <- params_html_head(html_head_script=c("script"))$children
expect_equal(result[[3]], shiny::tags$script(shiny::HTML("script")))
})

test_that("can pass string as script link", {
result <- params_html_head(html_head_script_link="script")$children
expect_equal(result[[3]], shiny::tags$script(src = "script"))
})

test_that("can pass vector of strings as script link", {
result <- params_html_head(html_head_script_link=c("script"))$children
expect_equal(result[[3]], shiny::tags$script(src = "script"))
})