Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make inputs in a sidebar persistent through tabs

Tags:

css

r

shiny

I would like to have both a persistent sidebar (as in shinydashboard layout) and a navigation bar with tabs (as in shiny::navbarPage layout). I came across this answer that seems to correspond to what I want.

The problem is that inputs in the sidebar are not persistent through tabs, i.e when switching tabs, the inputs in the sidebar are not displayed anymore (at the contrary of shinydashboard sidebar for example). Here's an example I cannot really minimize more since a lot of it is CSS:

library(shiny)
library(bootstraplib)

# boot dash layout funs ---------------------------------------------------


boot_side_layout <- function(...) {
  div(class = "d-flex wrapper", ...)
}

boot_sidebar <- function(...) {
  div(
    class = "bg-light border-right sidebar-wrapper",
    div(class = "list-group list-group-flush", ...)
  )
}

boot_main <- function(...) {
  div(
    class = "page-content-wrapper",
    div(class = "container-fluid", ...)
  )
}

# css ---------------------------------------------------------------------

css_def <- "
body {
  overflow-x: hidden;
}

.container-fluid, .container-sm, .container-md, .container-lg, .container-xl {
    padding-left: 0px;
}

.sidebar-wrapper {
  min-height: 100vh;
  margin-left: -15rem;
  padding-left: 15px;
  padding-right: 15px;
  -webkit-transition: margin .25s ease-out;
  -moz-transition: margin .25s ease-out;
  -o-transition: margin .25s ease-out;
  transition: margin .25s ease-out;
}


.sidebar-wrapper .list-group {
  width: 15rem;
}

.page-content-wrapper {
  min-width: 100vw;
  padding: 20px;
}

.wrapper.toggled .sidebar-wrapper {
  margin-left: 0;
}

.sidebar-wrapper, .page-content-wrapper {
  padding-top: 20px;
}

.navbar{
  margin-bottom: 0px;
}

.navbar-collapse {
  font-size: 1.1rem
}

@media (max-width: 768px) {
  .sidebar-wrapper {
    padding-right: 0px;
    padding-left: 0px;

  }
}

@media (min-width: 768px) { 
  .sidebar-wrapper {
    margin-left: 0;
    position: fixed;
  }

  .page-content-wrapper {
    min-width: 0;
    width: 100%;
  }

  .wrapper.toggled .sidebar-wrapper {
    margin-left: -15rem;
  }
}

"


# app ---------------------------------------------------------------------
ui <- tagList(
  tags$head(tags$style(HTML(css_def))),
  bootstrap(),
  navbarPage(
    collapsible = TRUE,
    title = "",
    tabPanel(
      "Statistics",
      boot_side_layout(
        boot_sidebar(
          selectInput(
            "variables",
            "Variables",
            NULL
          )
        ),
        boot_main(
          fluidRow(
            dataTableOutput("statistics")
          )
        )
      )
    ),
    
    tabPanel(
      "Plots",
      boot_side_layout(
        boot_sidebar(
          
        ),
        boot_main(
        )
      )
    )
  )
)

server <- function(input, output, session) {
  
  output$statistics <- renderDataTable(mtcars[10, 10])
  
}

shinyApp(ui, server)

How can I make these inputs persistent through sidebar? (If somebody knows of another simple way to mix persistent sidebar with navbar, please show it as well).

like image 552
bretauv Avatar asked Oct 25 '25 13:10

bretauv


1 Answers

Why not using a sidebarLayout with a navbarPage in mainPanel?

ui <- fluidPage(
  
  sidebarLayout(
    
    sidebarPanel(
      selectInput("select", "Select", c("a", "b", "c"))
    ),
    
    mainPanel(
      navbarPage(
        "App Title",
        tabPanel("Plot"),
        tabPanel("Summary"),
        tabPanel("Table")
      )    
    )
    
  )
)

shinyApp(ui, server)

EDIT

Or something like this?

library(shiny)
library(ggplot2)

ui <- fluidPage(
  
  div(
    style = "display: flex; flex-direction: column;",
    div( #~~ Main panel ~~#
      navbarPage(
        "Old Faithful Geyser Data",
        tabPanel(
          "Plot",
          plotOutput("ggplot")
        ),
        tabPanel("Summary"),
        tabPanel("Table")
      )    
    ),
    wellPanel( #~~ Sidebar ~~#
      style = "width: 300px;",
      sliderInput("bins", "Number of bins:", min = 1, max = 50, value = 30),
    )
  )
)

server <- function(input, output) {
  output[["ggplot"]] <- renderPlot({
    x    <- faithful[, 2] 
    bins <- seq(min(x), max(x), length.out = input$bins + 1)
    hist(x, breaks = bins, col = 'darkgray', border = 'white')
  })
}

shinyApp(ui = ui, server = server)

EDIT

Like this to have the sidebar on the left:

library(shiny)
library(shinyjs)
library(ggplot2)

CSS <- "
.sidebar {
  min-width: 300px;
  margin-right: 30px;
}
#sidebar {
  width: 300px;
}
"

ui <- fluidPage(
  
  useShinyjs(),
  
  tags$head(tags$style(HTML(CSS))),
  
  div( #~~ Main panel ~~#
    navbarPage(
      "Old Faithful Geyser Data",
      tabPanel(
        "Plot",
        div(
          style = "display: flex;",
          div(class = "sidebar"),
          plotOutput("ggplot")
        )
      ),
      tabPanel(
        "Summary",
        div(
          style = "display: flex;",
          div(class = "sidebar"),
          verbatimTextOutput("summary")
        )
      ),
      tabPanel(
        "Table",
        div(
          style = "display: flex;",
          div(class = "sidebar"),
          tableOutput("table")
        )
      ),
      id = "navbar"
    )    
  ),
  wellPanel( #~~ Sidebar ~~#
    id = "sidebar", 
    sliderInput("bins", "Number of bins:", min = 1, max = 50, value = 30),
  )
)

server <- function(input, output) {
  output[["ggplot"]] <- renderPlot({
    x    <- faithful[, 2] 
    bins <- seq(min(x), max(x), length.out = input$bins + 1)
    hist(x, breaks = bins, col = 'darkgray', border = 'white')
  })
  output[["summary"]] <- renderPrint({
    list(a = 1:10, b = 1:10)
  })
  output[["table"]] <- renderTable({
    iris[1:10,]
  })
  observeEvent(input[["navbar"]], {
    selector <- 
      sprintf("$('div.tab-pane[data-value=\"%s\"] div.sidebar')", input[["navbar"]])
    runjs(paste0(selector, ".append($('#sidebar'));"))
  })
}

shinyApp(ui = ui, server = server)

EDIT

Here is an improvement of the above way. I've made some convenient functions tabPanel2 and sidebar to help the user. And I use fluidRow and column instead of using a display: flex;. This allows to have a sidebar width relative to the screen size. The example below also shows how to not include the sidebar in a tab (simply use tabPanel and not tabPanel2.

library(shiny)
library(shinyjs)
library(ggplot2)

tabPanel2 <- function(title, ..., value = title, icon = NULL, sidebarWidth = 4){
  tabPanel(
    title = title, 
    fluidRow(
      column(
        width = sidebarWidth,
        class = "sidebar"
      ),
      column(
        width = 12 - sidebarWidth,
        ...
      )
    )
  )
}

sidebar <- function(...){
  div(
    style = "display: none;",
    tags$form(
      class = "well",
      id = "sidebar",
      ...
    )
  )
}

ui <- fluidPage(
  
  useShinyjs(),
  
  div( #~~ Main panel ~~#
    navbarPage(
      "Old Faithful Geyser Data",
      tabPanel2(
        "Plot",
        plotOutput("ggplot")
      ),
      tabPanel2(
        "Summary",
        verbatimTextOutput("summary")
      ),
      tabPanel(
        "Table",
        fluidRow(
          column(
            width = 4,
            wellPanel(
              tags$fieldset(
                tags$legend(h3("About")),
                p("This app is cool")
              )
            )
          ),
          column(
            width = 8,
            tableOutput("table")
          )
        )
      ),
      id = "navbar"
    )    
  ),
  
  sidebar( #~~ Sidebar ~~#
    sliderInput("bins", "Number of bins:", min = 1, max = 50, value = 30)    
  )

)

server <- function(input, output) {
  
  output[["ggplot"]] <- renderPlot({
    x    <- faithful[, 2] 
    bins <- seq(min(x), max(x), length.out = input$bins + 1)
    hist(x, breaks = bins, col = 'darkgray', border = 'white')
  })
  
  output[["summary"]] <- renderPrint({
    list(a = 1:10, b = 1:10)
  })
  
  output[["table"]] <- renderTable({
    iris[1:10,]
  })
  
  observeEvent(input[["navbar"]], {
    selector <- 
      sprintf("$('div.tab-pane[data-value=\"%s\"] div.sidebar')", input[["navbar"]])
    append <- "selector.append($('#sidebar'));"
    js <- sprintf("var selector=%s; if(selector.length){%s;}", selector, append)
    runjs(js)
  })
  
}

shinyApp(ui = ui, server = server)
like image 114
Stéphane Laurent Avatar answered Oct 27 '25 02:10

Stéphane Laurent