Below is a step by step introduction to the {bs4Dash} structure.

Create a basic page

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) {}
)
knitr::include_graphics("figures/basicPage.png")

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:

preloader <- list(html = tagList(spin_1(), "Loading ..."), color = "#343a40")

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!

Sidebar Setup

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:

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 :

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.

Navbar Setup

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()...

Right Sidebar Setup

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
    )
)

Footer Setup

We will use dashboardFooter():

dashboardFooter(
  left = a(
    href = "https://twitter.com/divadnojnarg",
    target = "_blank", "@DivadNojnarg"
  ),
  right = "2020"
)

Nothing special to add here!

Setting up the body content

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().

Wrap Up

Below is the code for your first {bs4Dash} application:

Code

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) {}
)


Original Image Light
Modified Image Dark


* 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.



hiplot/bs4Dash2 documentation built on Dec. 20, 2021, 3:51 p.m.