17 Create template elements
The list of all available tabler layouts is quite impressive (horizontal, vertical, compressed, right to left, dark, …). In the next steps, we will focus on the dark-compressed template, leaving the reader to try other templates as an exercise.
17.1 Identify template elements
We are quite lucky since there is nothing fancy about the Tabler layout. As usual, let’s inspect the layout-condensed-dark.html
(located /demo
folder) in Figure 17.1.
There are two main components:
- the header containing the brand logo, the navigation and dropdown.
- the content containing the dashboard body as well as the footer.
The dashboard body does not mean <body>
tag.
That is it for now.
17.2 Design the page layout
17.2.1 The page wrapper
Do you remember the structure of a basic HTML page seen in section 1.3? Well, if not, here is a reminder.
<!DOCTYPE HTML>
<html lang="en">
<head>
<!-- head content here -->
<title>A title</title>
</head>
<body>
<!-- body content here -->
</body>
</html>
We actually don’t need to include the <html>
tag since Shiny does it on the fly, as described earlier in section 5.7.1. Below we construct a list of tags with tagList()
, including the head and the body. In the head, we have the meta
tag, which has multiple purposes:
- Describe the encoding, which briefly controls what character can be displayed on the page. UTF-8 is a safe choice as it covers almost all existing characters.
- How to display the app on different devices. For instance the viewport meta tag handles the responsive web design.
width=device-width
, allows the page width to vary depending on the user device.initial-scale=1
handles the initial page zoom. - Set the favicon, which is an icon representing the website icon, that is the one you may see on the right side of the searchbar. Try Twitter for instance.
- …
The page title and favicon may be changed by the developer, so they may be included as function parameters. If you remember, there should also be CSS in the head but they are missing. Actually, the insertion of dependencies is achieved by our very own add_tabler_deps()
function defined in Chapter 16. Tabler comes with two main themes, namely white and dark, which may be applied through the <body>
class attribute (respectively, antialiased theme-dark
and antialiased
). The … parameter contains other template elements like the header and the dashboard body, which remain to be designed. As shown in Figure 16.1, the Tabler dashboard template may contain a navigation bar as well as a footer. As they are not mandatory, we don’t create dedicated parameters and pass all elements in the ...
slot:
tabler_page <- function(..., dark = TRUE, title = NULL,
favicon = NULL){
# head
head_tag <- tags$head(
tags$meta(charset = "utf-8"),
tags$meta(
name = "viewport",
content = "
width=device-width,
initial-scale=1,
viewport-fit=cover"
),
# ... Elements omitted for space reasons
tags$link(
rel = "shortcut icon",
href = favicon,
type="image/x-icon"
)
)
# body
body_tag <- add_tabler_deps(
tags$body(
tags$div(
class = paste0("antialiased ", if (dark) "theme-dark"),
style = "display: block;",
tags$div(class = "page", ...)
)
)
)
tagList(head_tag, body_tag)
}
The whole code maybe found in the {OSUICode}
side package.
Below we quickly test if a Tabler element renders well, to confirm whether our setup is adequate. To do this, we include a card element taken from the demo HTML page, using HTML()
.
Let’s be clear: this is only for testing purposes. In production, you should avoid this as much as possible because of security issues and the bad readability of the code.
This also checks that our basic Shiny input/output system works as expected with a textInput()
linked to a textOutput()
to provide the card title:
### RUN ###
# OSUICode::run_example(
# "tabler/test-template",
# package = "OSUICode"
# )
### APP CODE ###
library(shiny)
library(OSUICode)
card <- '
<div class="card">
<div class="card-status-top bg-danger"></div>
<div class="card-body">
<h3 class="card-title">
<div id="value" class="shiny-text-output"></div>
</h3>
<p>This is some text within a card body.</p>
</div>
</div>
'
ui <- tabler_page(
textInput("caption", "Title", "Card title"),
HTML(card),
title = "Tabler test"
)
server <- function(input, output) {
output$value <- renderText({ input$caption })
}
shinyApp(ui, server)
OK, our card and the shiny element work like a charm, which is a good start. Now we may focus on the aesthetics.
17.2.2 The body content
In this part, we translate the dashboard body HTML code to R. As a reminder, the html2r by Alan Dipert substantially speeds up the conversion process. You copy the code in the HTML text area, click on convert and get the R Shiny output. We create a function called tabler_body()
. The … parameter holds all the dashboard body elements, and the footer is dedicated for the future tabler_footer()
function.
tabler_body <- function(..., footer = NULL) {
div(
class = "content",
div(class = "container-xl", ...),
tags$footer(class = "footer footer-transparent", footer)
)
}
Let’s test it with the previous example.
ui <- tabler_page(tabler_body(h1("Hello World")))
server <- function(input, output) {}
shinyApp(ui, server)
Way better!
17.2.5 Card containers
Card are a central piece of template as they may contain visualizations, metrics and much more, generally enhancing content visibility. Thus, this is not a hazard why I choose this component and fortunately, Tabler offers a large choice of card containers.
17.2.5.1 Classic card
What we call a classic card is like the shinydashboard box()
container. The card structure has key elements:
- A width to control the space taken by the card in the Bootstrap grid.
- A title, in general in the header (Tabler does always not follow this rule and header is optional).
- A body where the main content is.
- Style elements like color statuses.
- A footer (optional, Tabler does not include this).
A comprehensive list of all Tabler card features may be found here. To be faster, we copy the following HTML code in the html2R Shiny app to convert it to Shiny tags:
<div class="col-md-6">
<div class="card">
<div class="card-status-top bg-danger"></div>
<div class="card-body">
<h3 class="card-title">Title</h3>
<p>Some Text.</p>
</div>
</div>
</div>
Below is the result. The next step consists of replacing all content by parameters to the tabler_card()
function, whenever necessary. For instance, the first <div>
sets the card width. The Bootstrap grid ranges from 1 to 12, so why not create a width parameter to control the card size. We proceed similarly for the title, status, body content. It seems reasonable to allow title to be NULL
(if so, the title is not shown), same thing for the status. Regarding the card default width, a value of six also makes sense, which would take half of the row:
tabler_card <- function(..., title = NULL, status = NULL,
width = 6, stacked = FALSE,
padding = NULL) {
card_cl <- paste0(
"card",
if (stacked) " card-stacked",
if (!is.null(padding)) paste0(" card-", padding)
)
status_tag <- if (!is.null(status)) {
div(class = paste0("card-status-top bg-", status))
}
body_tag <- div(
class = "card-body",
# we could have a smaller title like h4 or h5...
if (!is.null(title)) {
h3(class = "card-title", title)
},
...
)
main_wrapper <- div(class = paste0("col-md-", width))
card_wrapper <- div(class = card_cl)
card_wrapper <- tagAppendChildren(
card_wrapper, status_tag, body_tag
)
tagAppendChild(main_wrapper, card_wrapper)
}
In the meantime, it would be also convenient to be able to display cards in the same row. Let’s create the tabler_row()
function:
tabler_row <- function(...) {
div(class = "row row-deck", ...)
}
Below, we show an example of the tabler_card()
function, in combination with the apexcharter package. The whole code may be printed with OSUICode::get_example("tabler/card")
.
### RUN ###
# OSUICode::run_example(
# "tabler/card",
# package = "OSUICode"
# )
The code output is shown in Figure 17.6.
17.2.6 Ribbons: card components
Let’s finish this part by including a card component, namely the ribbon.
tabler_ribbon <- function(..., position = NULL, color = NULL,
bookmark = FALSE) {
ribbon_cl <- paste0(
"ribbon",
if (!is.null(position)) sprintf(" bg-%s", position),
if (!is.null(color)) sprintf(" bg-%s", color),
if (bookmark) " ribbon-bookmark"
)
div(class = ribbon_cl, ...)
}
Integrating the freshly created ribbon component requires modifying the card structure since the ribbon is added after the body tag, and no parameter is associated with this slot. We could also modify the tabler_card()
function, but htmltools offers tools to help us. Since the ribbon should be put after the card body, we may think about the tagAppendChild()
function, introduced in Chapter 2:
# add the ribbon to a card
my_card <- tabler_card(title = "Ribbon", status = "info")
my_card$children[[1]] <- tagAppendChild(
my_card$children[[1]],
tabler_ribbon(
icon("info-circle", class = "fa-lg"),
bookmark = TRUE,
color = "red"
)
)
Now, we check how it looks in a Shiny app.
### RUN ###
# OSUICode::run_example(
# "tabler/ribbon",
# package = "OSUICode"
# )
17.2.7 Icons
Not mentioned before, but we may include Font Awesome icons provided with Shiny, as well as other libraries. Moreover, Tabler has a internal svg library located here.
17.3 Exercises
- Have a look at this page. Select two elements and create the corresponding R functions.
- Leverage the new htmltools
tagQuery()
API (see section 2.6) to rewrite thetabler_navbar()
andtabler_card()
functions.