How to start?

David Granjon

2024-01-23

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) {}
)
Basic page template

Basic page template

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!

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.