2 htmltools overview

… However, if you already know HTML (or want to learn!) you can also work directly with HTML tags to achieve any level of customization you want
— Hadley Wickham

htmltools (Cheng, Sievert, et al. 2021) is a R package designed to:

  • Generate HTML tags from R.
  • Handle web dependencies (see chapter 4).

Historically, htmltools was extracted out of shiny (Chang et al. 2021) to be able to extend it, that is developing custom HTML tags, importing extra dependencies from the web. That’s why, both packages have many common functions! At the moment, htmltools does not have any user guide, although being a key package for all web things!

2.1 Writing HTML Tags from R

htmltools provides the necessary functions to write HTML tags that were introduced in Chapter 1.2. In R, it is even more convenient than raw HTML since there is no opening/closing tag, a simple function call instead:

library(htmltools)
tag <- div("Hello World")
tag

Hello World
## [1] "<div>Hello World</div>"

Inside the function call, all named elements become attributes, whereas unnamed elements become children. In some cases, tags may have empty attributes like <input disabled>. In that case, the corresponding R code is input(disabled = NA). Since tag functions produce shiny tags, that is HTML elements, we can capture the HTML output by converting it to a character with as.character(), as shown above.

2.2 Notations

Whether to use tags$div or div depends if the tag is exported by default. For instance, you could use div() but not nav since nav does not have a dedicated function (only for p, h1, h2, h3, h4, h5, h6, a, br, div, span, pre, code, img, strong, em, hr). Rather prefer tags$nav. Alternatively, there exists a function called withTags(). Wrapping your code in this function allows code like this withTags(nav(), ...) instead of tags$nav(), thereby omitting the tags$ prefixes. If you had to gather multiple tags together, choose tagList() over list(), although the HTML output is the same. The first has the shiny.tag.list class in addition to list. golem (Fay, Guyader, et al. 2020) allows to test if an R object is a tag list. In this case, using a list would cause the test fail.

2.3 Adding new tags

You may define extra HTML tags with the tag() function:

customTag <- tag(
  "test", 
  list(class = "test", p("Custom Tag"))
)
str(customTag)
## List of 3
##  $ name    : chr "test"
##  $ attribs :List of 1
##   ..$ class: chr "test"
##  $ children:List of 1
##   ..$ :List of 3
##   .. ..$ name    : chr "p"
##   .. ..$ attribs : Named list()
##   .. ..$ children:List of 1
##   .. .. ..$ : chr "Custom Tag"
##   .. ..- attr(*, "class")= chr "shiny.tag"
##  - attr(*, "class")= chr "shiny.tag"
## [1] "<test class="test">"
## [2] "  <p>Custom Tag</p>"
## [3] "</test>"

Good practice is to check whether the created tag is in line with the HTML validation rules.

2.4 Alternative way to write tags

htmltools comes with the HTML() function that you can feed with raw HTML. Below, both code give exactly the same output:

HTML("<div>Blabla</div>")
div("Blabla")

Internally, their classes are different:

class(HTML("<div>Blabla</div>"))
## [1] "html"      "character"
class(div("Blabla"))
## [1] "shiny.tag"

Doing so, you will not be able to use tags related functions, as in the next parts. Therefore, we strongly recommend using R and not mixing HTML in R.

Interestingly, if you want to convert raw HTML to R code, there is a Shiny App developed by Alan Dipert from RStudio, namely html2R, shown Figure 2.1. Non standard attributes (like data-toggle) are not correctly processed but there are solutions. This will save you precious time! A more recent approach is developed in section 22 and has be internally used to develop some of the RinteRface templates.

Illustration of the html2R App

FIGURE 2.1: Illustration of the html2R App

2.5 Playing with tags

2.5.1 Tags structure

A shiny tag is defined by:

  • A name such as span, div, h1 …, accessed with tag$name.
  • Some attributes, which can be accessed with tag$attribs.
  • Children, which can be accessed with tag$children.
  • A class, namely shiny.tag.

For instance:

# create the tag
myTag <- div(
  class = "divclass", 
  id = "first",
  h1("My first child!"),
  span(class = "child", id = "baby", "Crying")
)
# access its name
# myTag$name
# access its attributes (id and class)
# myTag$attribs
# access children (returns a list of 2 elements)
# myTag$children
# access its class
str(myTag)
## List of 3
##  $ name    : chr "div"
##  $ attribs :List of 2
##   ..$ class: chr "divclass"
##   ..$ id   : chr "first"
##  $ children:List of 2
##   ..$ :List of 3
##   .. ..$ name    : chr "h1"
##   .. ..$ attribs : Named list()
##   .. ..$ children:List of 1
##   .. .. ..$ : chr "My first child!"
##   .. ..- attr(*, "class")= chr "shiny.tag"
##   ..$ :List of 3
##   .. ..$ name    : chr "span"
##   .. ..$ attribs :List of 2
##   .. .. ..$ class: chr "child"
##   .. .. ..$ id   : chr "baby"
##   .. ..$ children:List of 1
##   .. .. ..$ : chr "Crying"
##   .. ..- attr(*, "class")= chr "shiny.tag"
##  - attr(*, "class")= chr "shiny.tag"
## [1] "<div class="divclass" id="first">"
## [2] "  <h1>My first child!</h1>"
## [3] "  <span class="child" id="baby">Crying</span>"
## [4] "</div>"

How to modify the class of the second child?

second_children <- myTag$children[[2]]
second_children$attribs$class <- "adult"
# This is not working ...
## [1] "<div class="divclass" id="first">"
## [2] "  <h1>My first child!</h1>"
## [3] "  <span class="child" id="baby">Crying</span>"
## [4] "</div>"

Why is this not working? By assigning myTag$children[[2]] to second_children, second_children$attribs$class <- "adult" modifies the class of the copy and not the original object. Thus we do:

myTag$children[[2]]$attribs$class <- "adult"
## [1] "<div class="divclass" id="first">"
## [2] "  <h1>My first child!</h1>"
## [3] "  <span class="adult" id="baby">Crying</span>"
## [4] "</div>"

2.5.2 Useful functions for tags

htmltools provides powerful functions to seamlessly manipulate tags. Let’s review some of them below.

2.5.2.1 Add attributes

tagAppendAttributes() adds a new attribute to the current tag. For instance, assuming we created a div without any id attribute:

myTag <- div("A tag")
myTag <- tagAppendAttributes(myTag, id = "myid")
## [1] "<div id="myid">A tag</div>"

You can pass as many attributes as you want, including non-standard attributes such as data-toggle (see Bootstrap 3 tabs for instance):

myTag <- tagAppendAttributes(
  myTag, 
  `data-toggle` = "tabs", 
  class = "myclass"
)
## [1] "<div id="myid" data-toggle="tabs" class="myclass">A tag</div>"

You could proceed as follows but this requires two steps:

myTag$attribs[["data-toggle"]] <- "newValue"
myTag$attribs$class <- "newClass"
## [1] "<div id="myid" data-toggle="newValue" class="newClass">A tag</div>"

2.5.2.2 Check if tag has specific attribute

tagHasAttribute() checks if a tag has a specific attribute:

# I want to know if div has a class
myTag <- div(class = "myclass")
tagHasAttribute(myTag, "class")
## [1] TRUE

If you are familiar with %>%, the above also works:

myTag %>% tagHasAttribute("class")
## [1] TRUE

In practice, this function is useful when testing tag elements as shown in chapter 21.

2.5.2.3 Get all attributes

tagGetAttribute() gets the targeted attribute’s value, if it exists, otherwise NULL:

myTag <- div(class = "test")
# returns the class
tagGetAttribute(myTag, "class")
## [1] "test"
# returns NULL
tagGetAttribute(myTag, "id")
## NULL

2.5.2.4 Set child/children

tagSetChildren() creates children for a given tag. For instance:

myTag <- div(
  class = "parent", 
  id = "father", 
  "Father!"
)
child <- span("Daughter")
myTag <- tagSetChildren(myTag, child)
## [1] "<div class="parent" id="father">"
## [2] "  <span>Daughter</span>"
## [3] "</div>"

tagSetChildren() removes all existing children. Below we see another set of functions to add children while conserving existing ones.

2.5.2.5 Add child or children

tagAppendChild() and tagAppendChildren() add other tags to an existing tag. Whereas tagAppendChild() only takes one tag, you can pass a list of tags to tagAppendChildren().

myTag <- div(class = "parent", "A tag", "Child 1")
otherTag <- span("Child 2")
myTag <- tagAppendChild(myTag, otherTag)
## [1] "<div class="parent">"
## [2] "  A tag"
## [3] "  Child 1"
## [4] "  <span>Child 2</span>"
## [5] "</div>"

2.5.2.6 Build your own functions

You might wonder why there is no tagRemoveChild or tagRemoveAttributes. Let’s look at the tagAppendChild:

tagAppendChild <- function (tag, child) {
  tag$children[[length(tag$children) + 1]] <- child
  tag
}

Below we write the tagRemoveChild, where tag is the target and n is the position to remove in the list of children:

myTag <- div(class = "parent", span("Hey!"))

# we create the tagRemoveChild function
tagRemoveChild <- function(tag, n) {
  # check if the list is empty
  if (length(tag$children) == 0) {
    stop(paste(tag$name, "does not have any children!"))
  }
  tag$children[n] <- NULL
  tag
}
myTag <- tagRemoveChild(myTag, 1)
## [1] "<div class="parent"></div>"

When defining the tagRemoveChild, we choose [ instead of [[ to allow to select multiple list elements. Also notice that the function raises an error if the provided tag does not have children.

The tagAppendChild() is not able to insert at a specific position. We could draft the tagInsertChild building on top of the base R append function:

tagInsertChild <- function(tag, child, position) {
  tag$children <- append(tag$children, list(child), position - 1)
  tag
}

res1 <- tagInsertChild(p(span("hello")), a(), 1)
res2 <- tagInsertChild(p(span("hello")), a(), 2)
## [1] "<p>"
## [2] "  <a></a>"
## [3] "  <span>hello</span>"
## [4] "</p>"
## [1] "<p>"
## [2] "  <span>hello</span>"
## [3] "  <a></a>"
## [4] "</p>"

2.5.3 Other functions

The golem package written by thinkr contains neat functions to edit your tags.

Particularly, the tagRemoveAttributes:

tagRemoveAttributes <- function(tag, ...) {
  attrs <- as.character(list(...))
  for (i in seq_along(attrs)) {
    tag$attribs[[ attrs[i] ]] <- NULL
  }
  tag
}
myTag <- div(class = "test", id = "coucou", "Hello")
myTag <- tagRemoveAttributes(myTag, "class", "id")
## [1] "<div>Hello</div>"

2.5.4 Conditionally set attributes

Sometimes, you only want to set attributes under specific conditions.

my_button <- function(color = NULL) {
  tags$button( 
    style = paste("color:", color),
    p("Hello")
  )
}

Calling my_button() would give:

## [1] "<button style="color: ">"
## [2] "  <p>Hello</p>"
## [3] "</button>"

This example will not fail but having style="color: " is not clean. We may use conditions:

my_button <- function(color = NULL) {
  tags$button( 
    style = if (!is.null(color)) paste("color:", color),
    p("Hello")
  )
}

Below, we call my_button("blue") and my_button():

## [1] "<button style="color: blue">"
## [2] "  <p>Hello</p>"
## [3] "</button>"
## [1] "<button>"
## [2] "  <p>Hello</p>"
## [3] "</button>"

In this example, style won’t be available if color is not specified.

2.5.5 Using %>%

While doing a lot of manipulation for a tag, if you don’t need to create intermediate objects, this is a good idea to use %>% from magrittr:

myTag <- div(class = "cl", h1("Hello")) %>% 
  tagAppendAttributes(id = "myid") %>%
  tagAppendChild(p("some extra text here!"))
## [1] "<div>Hello</div>"

This is overall easier to follow and read.

2.5.6 Programmatically create children elements

Assume you want to create a tag with three children inside:

myTag <- div(
  span(1),
  span(2),
  span(3),
  span(4),
  span(5)
)
## [1] "<div>Hello</div>"

The structure is correct but imagine if you had to create 1000 span() or fancier tag. The previous approach is not consistent with the DRY programming concept. lapply() function will be useful here (or the purrr map() family):

# base R
div(lapply(1:5, function(i) span(i)))
# purrr + %>%
map(1:5, function(i) span(i)) %>% div()
## [1] "<div>"
## [2] "  <span>1</span>"
## [3] "  <span>2</span>"
## [4] "  <span>3</span>"
## [5] "  <span>4</span>"
## [6] "  <span>5</span>"
## [7] "</div>"

2.6 Practical examples

Below we give concrete example on how to customize tags in the real life. There exists a nice RPG HTML template, that is rpgui. It provides the necessary elements to get started developing nice RPG looking user interfaces, as depicted by Figure 2.2.

rpgui select input

FIGURE 2.2: rpgui select input

In the following, we consider the select input, which does not have exactly the same structure as the original shiny tag. However, it is convenient to reuse the shiny function to limit our amount of work. We therefore start to write our custom input:

rpgSelect <- function(inputId, label, choices, selected = NULL,
                      multiple = FALSE, size = NULL) {
  shiny::selectInput(
    inputId,
    label,
    choices,
    selected,
    multiple,
    selectize = FALSE,
    width = NULL,
    size
  )
}

According to the rpgui documentation, a select tag is composed of the following HTML elements:

<select class="rpgui-dropdown">
    <option value="option1">option1</option>
    <option value="option2">option2</option>
    ...
</select>

Adding a label tag on top of the slider, this is what we would like to get:

<div>
  <label id="variable-label" for="variable">Variable:</label>
  <select 
    id="variable" 
    class="rpgui-dropdown">
    <option value="cyl" selected>Cylinders</option>
    <option value="am">Transmission</option>
    <option value="gear">Gears</option>
  </select>
</div>

We compare with our own rpgSelect function:

rpgSelect(
  "variable", 
  "Variable:",
  c("Cylinders" = "cyl",
    "Transmission" = "am",
    "Gears" = "gear")
)
## [1] "<div class="form-group shiny-input-container">"
## [2] "  <label class="control-label" id="variable-label" for="variable">Variable:</label>"
## [3] "  <div>"
## [4] "    <select id="variable" class="form-control"><option value="cyl" selected>Cylinders</option>"
## [5] "<option value="am">Transmission</option>"
## [6] "<option value="gear">Gears</option></select>"
## [7] "  </div>"
## [8] "</div>"

As shown in the above output, this is not exactly matching:

  • The outer div should not have any class.
  • The label should not have any class.
  • The input tag is wrapped inside a div container. It should not.
  • The input tag should have the rpgui-dropdown or rpgui-list class, depending on the size value.

To fix the first problem we target the outer tag (selectTag), that is the tag returned by our rpgSelect function. The second row cleans the label class. The third row removes the extra outer div and only keeps its children, corresponding to the input tag. The last instruction ensure to set the appropriate class, depending on the size value:

# Modify tag
selectTag$attribs$class <- NULL
# Clean extra label class
selectTag$children[[1]]$attribs$class <- NULL
# Remove extra outer div
selectTag$children[[2]] <- selectTag$children[[2]]$children[[1]]

# Add good class for rppgui binding
selectTag$children[[2]]$attribs$class <- if (is.null(size)) {
  "rpgui-dropdown"
} else {
  "rpgui-list"
}

The final version is shown below:

rpgSelect <- function(inputId, label, choices, selected = NULL,
                      multiple = FALSE, size = NULL) {
  selectTag <- shiny::selectInput(
    inputId,
    label,
    choices,
    selected,
    multiple,
    selectize = FALSE,
    width = NULL,
    size
  )

  # Modify tag
  selectTag$attribs$class <- NULL
  # Clean extra label class
  selectTag$children[[1]]$attribs$class <- NULL
  # Remove extra outer div
  selectTag$children[[2]] <- selectTag$children[[2]]$children[[1]]

  # Add good class for rppgui binding
  selectTag$children[[2]]$attribs$class <- if (is.null(size)) {
    "rpgui-dropdown"
  } else {
    "rpgui-list"
  }

  selectTag
}

This yields:

## [1] "<div>"
## [2] "  <label id="variable-label" for="variable">Variable:</label>"
## [3] "  <select id="variable" class="rpgui-dropdown"><option value="cyl" selected>Cylinders</option>"
## [4] "<option value="am">Transmission</option>"
## [5] "<option value="gear">Gears</option></select>"
## [6] "</div>"

2.7 Exercises

2.7.1 Exercise 1: tags structure

Consider the following shiny tag:

myTag <- a(
  class = "btn btn-large",
  type = "button",
  span(class = "child1", id = "super-span", 1),
  span(class = "child2", 2)
)

myTag
  1. Inspect its structure. Hint: you may use str().
  2. Access its class using tagGetAttribute() and another method of your choice.
  3. Modify the first child class to custom class.

2.7.2 Exercise 2: modifiying tags

Let us consider the following tag:

temp <- div("Hello World")

(You may chain functions with %>%)

  1. Replace its unique child by a(href = "http://www.google.com", "click me!"). Hint: tagSetChildren() is your friend.
  2. Add 10 other span(). Hint: tags may be programmatically generated with lapply() or purrr::map.