Below is a step by step introduction to the bs4Dash structure.
This is the template to start with bs4Dash:
library(shiny)
library(bs4Dash)
shinyApp(
ui = dashboardPage(
title = "Basic Dashboard",
header = dashboardHeader(),
sidebar = dashboardSidebar(),
controlbar = dashboardControlbar(),
footer = dashboardFooter(),
body = dashboardBody()
),
server = function(input, output) {}
)
The dashboardPage()
is the main wrapper:
dashboardPage(
header,
sidebar,
body,
controlbar = NULL,
footer = NULL,
title = NULL,
freshTheme = NULL,
preloader = NULL,
options = NULL,
fullscreen = FALSE,
help = FALSE,
dark = FALSE,
scrollToTop = FALSE
)
has mandatory slots for the navbar (dashboardHeader()
),
sidebar (dashboardSidebar()
) and
(dashboardBody()
). Note the
dashboardControlbar()
and dashboardFooter()
are optional. The title parameter gives its name to the
web browser tab. freshTheme, when provided, expects a
fresh powered theme
created with fresh::create_theme()
. It allows deeper
customization of colors to fit very specific needs like industry brand
colors. preloader expects a loader tag built with
waiter
, see more here, for instance:
At the moment, options are not available, but the idea is to provide deeper customization of the AdminLTE3 template like changing the sidebars and cards animation speed, …
When fullscreen is TRUE, an icon is displayed in the
navbar to switch to full screen mode. help
automatically enable/disable all tooltips and popover that are present
in the shiny app: this is an easier approach than using the server
methods addPopover()
, addTooltip()
, … but less
specific. dark allows to toggle the dark mode: if
FALSE, the theme switch is hidden and the dashboard takes the light
design. scrollToTop allows to toggle the scroll to top
button shown in the bottom right corner.
Now, it is time to fill this template!
Below we quickly describe the dashboardSidebar()
function:
dashboardSidebar(
disable = FALSE,
width = NULL,
skin = "dark",
status = "primary",
elevation = 4,
collapsed = FALSE,
minified = TRUE,
expandOnHover = TRUE,
fixed = TRUE,
id = "sidebar",
customArea = NULL,
...
)
A lot of options are available:
menuItem()
have. There are 20 different colors listed in
getAdminLTEColors()
.updateSidebar()
function to programmatically toggle the sidebar on the server.
input$<id>
indicates the state of the sidebar: TRUE
means open and FALSE means collapsed/minified.The skin switch feature allows to automatically toggle the sidebar skin.
Importantly, the sidebar contains sidebarMenu()
as well
as other items like sidebarUserPanel()
,
sidebarHeader()
:
sidebarUserPanel(
image = "https://image.flaticon.com/icons/svg/1149/1149168.svg",
name = "Welcome Onboard!"
)
sidebarMenu(
id = "sidebarmenu",
sidebarHeader("Header 1"),
menuItem(
"Item 1",
tabName = "item1",
icon = icon("sliders")
),
menuItem(
"Item 2",
tabName = "item2",
icon = icon("id-card")
)
)
sidebarMenu()
drives the navigation within your
dashboard. It has an id parameter which allows to :
input$<id>
.updateTabItems()
, which is actually
shiny::updateTabsetPanel
.sidebarMenu()
also offers 4 cosmetic parameters:
Interestingly, menuItem()
can be more than a simple item
and contain sub-items, namely menuSubItem()
:
menuItem(
text = "Item List 1",
icon = icon("bars"),
startExpanded = TRUE,
menuSubItem(
text = "Item 3",
tabName = "tab3",
icon = icon("circle-thin")
),
menuSubItem(
text = "Item 4",
tabName = "tab4",
icon = icon("circle-thin")
)
)
startExpanded defines whether the item container has
to be opened when the app starts. When a menuItem()
contains nested items, it is not necessary to give it a
tabName. text may also contain more
complex HTML tags like dashboardBadge()
. If you want to use
menuItem()
to browse to an external website, use the
href parameter as well as newTab to
open a new web browser tab.
Like in shinydashboard,
input$sidebarItemExpanded
hosts the value of the currently
expanded sidebarItem.
The dashboardHeader()
function creates a navbar for
bs4Dash:
dashboardHeader(
title = NULL,
titleWidth = NULL,
disable = FALSE,
.list = NULL,
skin = "light",
status = "white",
border = TRUE,
compact = FALSE,
sidebarIcon = shiny::icon("bars"),
controlbarIcon = shiny::icon("th"),
fixed = FALSE,
leftUi = NULL,
rightUi = NULL
)
The title parameter can host simple text but more
complex content like dashboardBrand()
:
title <- dashboardBrand(
title = "My dashboard",
color = "primary",
href = "https://adminlte.io/themes/v3",
image = "https://adminlte.io/themes/v3/dist/img/AdminLTELogo.png"
)
dashboardBrand()
is an enhanced title which has a color
status, points to an optional url and may contain a logo. The title
width can be controlled by titleWidth, like in
shinydashboard. Like dashboardSidebar()
,
dashboardHeader()
offers a lot of theming options with
skin and status, but also with
border and compact. They respectively
show a bottom border and smaller text. sidebarIcon and
controlbarIcon control icons for sidebar and
controlbar, respectively. The fixed parameter is useful
when one wants to see the navbar even at the bottom of the dashboard,
without having to scroll up.
leftUi
, ...
and rightUi
are
containers that can contains content from left to right. Ideally, we put
dropdownMenu()
as well as taskItem()
,
messageItem()
, notificationItem()
,
dashboardUser()
…
dashboardControlbar()
provides an extra sidebar, on the
right side:
dashboardControlbar(
id = NULL,
disable = FALSE,
width = 250,
collapsed = TRUE,
overlay = TRUE,
skin = "dark",
pinned = NULL
)
Like the dashboardSidebar()
,
dashboardControlbar()
may be programmatically toggled on
the server with updateControlbar()
, provided that the
id parameter has a value. In practice, if no id is
passed by the user, bs4Dash assigns a specific id. One
can control the dashboardControlbar()
state at start with
collapsed. If TRUE, the controlbar is collapsed and
inversely. By default, overlay is TRUE, meaning that
the controlbar opens on top of the body content. If FALSE, it pushes and
the body content to the left. pinned allows the
controlbar to remain open even after a click outside (clicking outside
collapses the controlbar by default). This is useful to keep focus on
important options whenever necessary. Finally, the controlbar is
entirely themable, like dashboardSidebar()
and
dashboardHeader()
.
dashboardControlbar()
contains
controlbarMenu()
that hosts controlbarItem()
.
This feature is built on top the shiny::tabsetPanel
, that
has been rebranded for Bootstrap 4 compatibility:
controlbarMenu(
...,
id = NULL,
selected = NULL,
type = c("tabs", "pills"),
position = NULL,
vertical = FALSE,
side = "left",
.list = NULL
)
controlbarMenu()
may be updated on the server with
updateControlbarMenu()
(which is no more than
shiny::updateTabsetPanel
). If you want to have a simple
container without menu, you will have to add a specific class to account
for padding, as shown below:
dashboardControlbar(
div(
class = "p-3",
# any content
)
)
We will use dashboardFooter()
:
dashboardFooter(
left = a(
href = "https://twitter.com/divadnojnarg",
target = "_blank", "@DivadNojnarg"
),
right = "2020"
)
Nothing special to add here!
dashboardBody()
is the main dashboard container:
dashboardBody(
tabItems(
tabItem(
tabName = "item1",
fluidRow(
lapply(1:3, FUN = function(i) {
sortable(
width = 4,
p(class = "text-center", paste("Column", i)),
lapply(1:2, FUN = function(j) {
box(
title = paste0("I am the ", j, "-th card of the ", i, "-th column"),
width = 12,
"Click on my header"
)
})
)
})
)
),
tabItem(
tabName = "item2",
box(
title = "Card with messages",
width = 9,
userMessages(
width = 12,
status = "success",
userMessage(
author = "Alexander Pierce",
date = "20 Jan 2:00 pm",
image = "https://adminlte.io/themes/AdminLTE/dist/img/user1-128x128.jpg",
type = "received",
"Is this template really for free? That's unbelievable!"
),
userMessage(
author = "Dana Pierce",
date = "21 Jan 4:00 pm",
image = "https://adminlte.io/themes/AdminLTE/dist/img/user5-128x128.jpg",
type = "sent",
"Indeed, that's unbelievable!"
)
)
)
)
)
)
The principle is pretty straightforward: all
dashboardBody()
elements must be embeded in a
tabItems()
list containing as may elements as the number of
items. Each item is a tabItem()
. Importantly, the
tabName argument must be provide and unique. Moreover,
it must be identical to the corresponding menuItem()
, so
that the navigation between tabs work. This is exactly the same
principle as for shinydashboard. Therefore, users should
not be lost.
In practice, if the sidebar is empty (without menu), it is still
possible to get rid of tabItems()
and
tabItem()
.
Below is the code for your first bs4Dash application:
shinyApp(
ui = dashboardPage(
title = "Basic Dashboard",
fullscreen = TRUE,
header = dashboardHeader(
title = dashboardBrand(
title = "bs4Dash",
color = "primary",
href = "https://www.google.fr",
image = "https://adminlte.io/themes/AdminLTE/dist/img/user2-160x160.jpg",
),
skin = "light",
status = "white",
border = TRUE,
sidebarIcon = icon("bars"),
controlbarIcon = icon("th"),
fixed = FALSE,
leftUi = tagList(
dropdownMenu(
badgeStatus = "info",
type = "notifications",
notificationItem(
inputId = "triggerAction2",
text = "Error!",
status = "danger"
)
),
dropdownMenu(
badgeStatus = "info",
type = "tasks",
taskItem(
inputId = "triggerAction3",
text = "My progress",
color = "orange",
value = 10
)
)
),
rightUi = dropdownMenu(
badgeStatus = "danger",
type = "messages",
messageItem(
inputId = "triggerAction1",
message = "message 1",
from = "Divad Nojnarg",
image = "https://adminlte.io/themes/v3/dist/img/user3-128x128.jpg",
time = "today",
color = "lime"
)
)
),
sidebar = dashboardSidebar(
skin = "light",
status = "primary",
elevation = 3,
sidebarUserPanel(
image = "https://image.flaticon.com/icons/svg/1149/1149168.svg",
name = "Welcome Onboard!"
),
sidebarMenu(
sidebarHeader("Header 1"),
menuItem(
"Item 1",
tabName = "item1",
icon = icon("sliders")
),
menuItem(
"Item 2",
tabName = "item2",
icon = icon("id-card")
)
)
),
controlbar = dashboardControlbar(
skin = "light",
pinned = TRUE,
collapsed = FALSE,
overlay = FALSE,
controlbarMenu(
id = "controlbarmenu",
controlbarItem(
title = "Item 1",
sliderInput(
inputId = "obs",
label = "Number of observations:",
min = 0,
max = 1000,
value = 500
),
column(
width = 12,
align = "center",
radioButtons(
inputId = "dist",
label = "Distribution type:",
c(
"Normal" = "norm",
"Uniform" = "unif",
"Log-normal" = "lnorm",
"Exponential" = "exp"
)
)
)
),
controlbarItem(
"Item 2",
"Simple text"
)
)
),
footer = dashboardFooter(
left = a(
href = "https://twitter.com/divadnojnarg",
target = "_blank", "@DivadNojnarg"
),
right = "2018"
),
body = dashboardBody(
tabItems(
tabItem(
tabName = "item1",
fluidRow(
lapply(1:3, FUN = function(i) {
sortable(
width = 4,
p(class = "text-center", paste("Column", i)),
lapply(1:2, FUN = function(j) {
box(
title = paste0("I am the ", j, "-th card of the ", i, "-th column"),
width = 12,
"Click on my header"
)
})
)
})
)
),
tabItem(
tabName = "item2",
box(
title = "Card with messages",
width = 9,
userMessages(
width = 12,
status = "success",
userMessage(
author = "Alexander Pierce",
date = "20 Jan 2:00 pm",
image = "https://adminlte.io/themes/AdminLTE/dist/img/user1-128x128.jpg",
type = "received",
"Is this template really for free? That's unbelievable!"
),
userMessage(
author = "Dana Pierce",
date = "21 Jan 4:00 pm",
image = "https://adminlte.io/themes/AdminLTE/dist/img/user5-128x128.jpg",
type = "sent",
"Indeed, that's unbelievable!"
)
)
)
)
)
)
),
server = function(input, output) {}
)
* All credits go to https://codyhouse.co/gem/css-jquery-image-comparison-slider/ for the nice image slider widget!
Advanced shiny user would probably design shiny modules to generate this page, which I really encourage. However, how to deal with modules is not the purpose of this article.