I want to show the controlbar (right sidebar) only for several tabs in the left sidebar.
library(shiny)
library(bs4Dash)
shinyApp(
ui = dashboardPage(
header = dashboardHeader(
title = "My dashboard"
),
sidebar = dashboardSidebar(
sidebarMenu(
id = "sidebarMenu",
menuItem(
text = "Tab 1",
tabName = "tab1"
),
menuItem(
text = "Tab 2",
tabName = "tab2"
)
)
),
body = dashboardBody(),
controlbar = dashboardControlbar(),
title = "DashboardPage"
),
server = function(input, output) { }
)
I try to develop a shiny app where for some of the tabs a controlbar is necessary, for others the controlbar should show certain options and for others it is obvious at all. What is the most elegant way to ensure this? Should i "outsource" the appearance into seperate modules or are there other recommendations?
It is possible, and there are a lot of different ways to achieve this.
Different methods have their benefits and shortcomings.
I can demonstrate to basic Shiny methods for conditional UI elements that are used the most.
The common thing among the "easy" solutions to the conditional UI per tab problem is to evaluate somehow on which tab the users view currently is.
First method would be to check the Javascript context and adapt the UI using conditionalPanel:
shinyApp(
ui = dashboardPage(
header = dashboardHeader(
title = "My dashboard"
),
sidebar = dashboardSidebar(
sidebarMenu(
id = "sidebarMenu",
menuItem(
text = "Tab 1",
tabName = "tab1"
),
menuItem(
text = "Tab 2",
tabName = "tab2"
),
menuItem(
text = "Tab 3",
tabName = "tab3"
)
)
),
body = dashboardBody(),
controlbar = dashboardControlbar(
id= "controlbar",
collapsed = TRUE,
conditionalPanel(
condition = "input.sidebarMenu=='tab1'||input.sidebarMenu=='tab3'",
controlbarMenu(
controlbarItem(title = "Item1"),
controlbarItem(title = "Item2")
)
),
conditionalPanel(
condition = "input.sidebarMenu=='tab2'",
controlbarMenu(
controlbarItem(title = "Item3")
)
),
conditionalPanel(
condition = "input.sidebarMenu=='tab3'",
controlbarMenu(
controlbarItem(title = "Item4")
)
)
),
title = "DashboardPage"
),
server = function(input, output,session) { }
)
The benefit is clearly that this should render faster since it gets evaluated inside the UI (clientside). The downside is that we have to use Javascript (what we might dont want to do) and also we create logic in the UI function, making it more cluttered.
The next method would be to use R and render some parts of the UI in the Server function and send it to an output function in the UI
shinyApp(
ui = dashboardPage(
header = dashboardHeader(
title = "My dashboard"
),
sidebar = dashboardSidebar(
sidebarMenu(
id = "sidebarMenu",
menuItem(
text = "Tab 1",
tabName = "tab1"
),
menuItem(
text = "Tab 2",
tabName = "tab2"
),
menuItem(
text = "Tab 3",
tabName = "tab3"
)
)
),
body = dashboardBody(),
controlbar = dashboardControlbar(
id= "controlbar",
collapsed = TRUE,
sidebarMenuOutput("Menu")
),
title = "DashboardPage"
),
server = function(input, output,session) {
observeEvent(input$sidebarMenu, {
output$Menu <- renderMenu({
if(input$sidebarMenu=="tab1") {
controlbarMenu(
controlbarItem(
title = "Item1"
)
)
}else if(input$sidebarMenu=="tab2"){
controlbarMenu(
controlbarItem(
title = "Item2"
),
controlbarItem(
title = "Item2_2"
)
)
}else{
controlbarMenu()
}
})
})
}
)
The benefit is that we have the logic in the Server function, and our UI gets more concise. The downsight is that we add extra computational work to the server, which could be done on the clients side. We also will have to write some if else statments or map or apply and its not clear what amount of the UI should be rendered in the server function. Also it gets more complex if you want to add a feature, so often times you have rewrite this alot during development if your not careful and plan ahead for reactivity, etc.
Related
I have a shiny app with popovers that contain useful information on interpretation of plot output. However, i have to close the popover "manually" everytime. Otherwise the popover of tab "dash1" will stay even if i switch to tab "dash2".
library(shiny)
library(bs4Dash)
ui <- dashboardPage(
dashboardHeader(title = "Dashboard",
titleWidth = 550,
disable= FALSE,
sidebarIcon = NULL
),
dashboardSidebar(
sidebarMenu(
menuItem("dash1", tabName= "dashboard1"),
menuItem("dash2", tabName= "dashboard2")
)
),
dashboardBody(
tabItems(
tabItem(tabName = "dashboard1",
box(
title = "Interpretation",
popover(
actionButton("goButton", "Click here"),
title = "Important information",
placement = "right",
content = "popover text bla bla"
)
)
),
tabItem(tabName = "dashboard2",
box(
title = "Interpretation",
popover(
actionButton("goButton2", "Click here"),
title = "Important information",
placement = "right",
content = "popover text bla bla"
)
)
)
)
)
)
server <- function(input, output) {
}
shinyApp(ui = ui, server = server)
How can i prevent this "behavior"? Do i necessarily have to specify addPopover and removePopover within the server part?
I solved the problem with the help of this issue:
Display Text only on hover
What i need is a tooltip rather than a popover.
This is my {shinydashboard}:
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(
"User Name",
subtitle = a(href = "#", icon("circle", class = "text-success"), "Online"),
sidebarMenu(
id = "tabs",
menuItem(
"Dashboard",
tabName = "dashboard",
icon = icon("dashboard"),
menuSubItem(
"Widgets",
icon = icon("th"),
tabName = "widgets"
),
menuSubItem(
"Charts",
icon = icon("th"),
tabName = "charts"
)
))),
dashboardBody(
tabItems(
tabItem("dashboard",
tags$button(
h1('This button takes me to the Widgets Panel')),
br(),
br(),
tags$button(h1('This button takes me to the Charts Panel'))
))))
server = function(input, output) {
}
shinyApp(ui, server)
The idea is to click on 'Dashboard' Menu and then on the body I have a link or button that takes me to the 'Widgets' or 'Charts' Panels.
Once I click on those buttons/links OR click on the Sub Menus of 'Dashboard' menu these actions will take me to their Panels.
So, how can I add the buttons and links that take me to the panels of my Menu SubItems?
And how can I add the buttons and links on the body of my Shiny Dashboard?
Any help would me amazing.
Your question is the result of a common misunderstanding regarding shinydashboard's structure.
Childfull menuItems (like your "Dashboard" menuItem) don't have a corresponding tabItem.
The only usecase for a childfull menuItem is to expand on click and present its children (no visual change in the body - only in the sidebar).
Accordingly in your above code the tabName = "dashboard" parameter is ignored and the tabItem("dashboard", ...) isn't displayed.
When a menuItem is childfull it accepts the parameters
expandedName and startExpanded.
When a menuItem is childless it accepts the parameters tabName
and selected (just like menuSubItem which always is childless).
Please read the small print.
Also check my related answers here and here.
However, there are unofficial workarounds to have a childfull menuItem display a tabItem - I don't recommend to use them.
To navigate your tabs programmatically use updateTabItems:
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
header = dashboardHeader(),
sidebar = dashboardSidebar(
sidebarUserPanel(name = "User Name",
subtitle = a(href = "#", icon("circle", class = "text-success"), "Online"),
image = NULL),
sidebarMenu(
id = "tabs",
menuItem(
"Dashboard",
menuSubItem("Widgets",
icon = icon("th"),
tabName = "widgets"),
menuSubItem("Charts",
icon = icon("th"),
tabName = "charts"),
icon = icon("dashboard"),
expandedName = "dashboard",
startExpanded = TRUE
)
)
),
body = dashboardBody(
tabItems(
tabItem("widgets", actionButton("goCharts", "Go to Charts tab")),
tabItem("charts", actionButton("goWidgets", "Go to Widgets tab"))
)
)
)
server <- function(input, output, session) {
observeEvent(input$goCharts, {
updateTabItems(session, inputId = "tabs", selected = "charts")
})
observeEvent(input$goWidgets, {
updateTabItems(session, inputId = "tabs", selected = "widgets")
})
}
shinyApp(ui, server)
In the Shiny app below I am updating tabPanel content when the selection in sidebarMenu changes. However, there is a minor delay in the tabPanel content update when I change the selection in sidebarMenu.
For the small number of input values, this delay is negligible, but when I have a selectizeInput control in sidebarMenu and I load 1000 values there, then the content update in tabPanel is substantial. Is there a way to update tabPanel content instantly? Something like - content in all tabs is updated as soon as someone makes a selection in sidebarMenu, even before someone clicks at the tab?
library(shiny)
library(shinydashboard)
siderbar <- dashboardSidebar(
sidebarMenu(
# Add buttons to choose the way you want to select your data
radioButtons("select_by", "Select by:",
c("Food Type" = "Food",
"Gym Type" = "Gym",
"TV show" = "TV"))
)
)
body <- dashboardBody(
fluidRow(
tabBox(
side = "right",
selected = "Tab3",
tabPanel("Tab1", "Tab content 1", textOutput("tabset1Selected")),
tabPanel("Tab2", "Tab content 2", textOutput("tabset2Selected")),
tabPanel("Tab3", "Tab content 3", textOutput("tabset3Selected"))
)
),
)
shinyApp(
ui = dashboardPage(
dashboardHeader(title = "tabBoxes"),
siderbar,
body
),
server = function(input, output) {
# The currently selected tab from the first box
output$tabset1Selected <- output$tabset2Selected <- output$tabset3Selected <- renderText({
input$select_by
})
}
)
Using the outputOptions to set suspendWhenHidden = FALSE updates the outputs also if they aren't visible:
library(shiny)
library(shinydashboard)
siderbar <- dashboardSidebar(
sidebarMenu(
# Add buttons to choose the way you want to select your data
selectizeInput(inputId = "select_by", label = "Select by:",
choices= as.character(1:1000))
)
)
body <- dashboardBody(
fluidRow(
tabBox(
side = "right",
selected = "Tab3",
tabPanel("Tab1", "Tab content 1", textOutput("tabset1Selected")),
tabPanel("Tab2", "Tab content 2", textOutput("tabset2Selected")),
tabPanel("Tab3", "Tab content 3", textOutput("tabset3Selected"))
)
),
)
shinyApp(
ui = dashboardPage(
dashboardHeader(title = "tabBoxes"),
siderbar,
body
),
server = function(input, output) {
# The currently selected tab from the first box
output$tabset1Selected <- output$tabset2Selected <- output$tabset3Selected <- renderText({
input$select_by
})
lapply(list("tabset1Selected", "tabset2Selected", "tabset3Selected"), outputOptions, x = output, suspendWhenHidden = FALSE)
}
)
Furthermore you should consider using a server-side selectizeInput to enhance the performance for many choices.
I am building an app using the bs4Dash package, and I would like to include action buttons in the main page that would allow the user to jump to the appropriate page. However, the buttons do not do anything.
This is very much the same as this question. I believe the issue here is that updatebs4TabItems requires a TabSetPanel inputId... Except that I do not wish to include tabset panels here.
library(shiny)
library(bs4Dash)
ui <- bs4DashPage(
# Sidebar -------------------------------------------------------------
sidebar = bs4DashSidebar(
bs4SidebarMenu(
bs4SidebarMenuItem(
"Welcome",
tabName = "item0"
),
bs4SidebarMenuItem(
"Page 1",
tabName = "item1"
),
bs4SidebarMenuItem(
"Page 2",
tabName = "item2"
)
)
),
# Body -------------------------------------------------------------
body = bs4DashBody(
bs4TabItems(
bs4TabItem(
tabName = "item0",
fluidRow(
actionButton("JumpToV1", "Go to Page 1"),
actionButton("JumpToV2", "Go to Page 2")
)
),
bs4TabItem(
tabName = "item1",
fluidRow(
bs4Callout(
title = "This is Page 1",
elevation = 4,
status = "danger"
)
)
),
bs4TabItem(
tabName = "item2",
fluidRow(
bs4Callout(
title = "This is Page 2",
elevation = 4,
status = "danger")
)
)
)
)
)
server <- function(input, output, session) {
observeEvent(input$JumpToV1, {
updatebs4TabItems(session, "item0", selected = "item1")
})
observeEvent(input$JumpToV2, {
updatebs4TabItems(session, "item0", selected = "item2")
})
}
shinyApp(ui, server)
You're close! Two things need adjusting. From the documentation, note that the selected argument in updatebs4TabItems takes an integer (the position of the selected tab), not a string (so not the id name you have). Also, the inputID argument for updatebs4TabItems will refer to the sidebarID for you, which you need to set. The below code should work how you want.
library(shiny)
library(bs4Dash)
ui <- bs4DashPage(
# Sidebar -------------------------------------------------------------
sidebar = bs4DashSidebar(
bs4SidebarMenu(
id = "sidebarID", #note the new ID here
bs4SidebarMenuItem(
"Welcome",
tabName = "item0"
),
bs4SidebarMenuItem(
"Page 1",
tabName = "item1"
),
bs4SidebarMenuItem(
"Page 2",
tabName = "item2"
)
)
),
# Body -------------------------------------------------------------
body = bs4DashBody(
bs4TabItems(
bs4TabItem(
tabName = "item0",
fluidRow(
actionButton("JumpToV1", "Go to Page 1"),
actionButton("JumpToV2", "Go to Page 2")
)
),
bs4TabItem(
tabName = "item1",
fluidRow(
bs4Callout(
title = "This is Page 1",
elevation = 4,
status = "danger"
)
)
),
bs4TabItem(
tabName = "item2",
fluidRow(
bs4Callout(
title = "This is Page 2",
elevation = 4,
status = "danger")
)
)
)
)
)
server <- function(input, output, session) {
observeEvent(input$JumpToV1, {
#changed ID and selected here and below
updatebs4TabItems(session, inputId = "sidebarID", selected = 2)
})
observeEvent(input$JumpToV2, {
updatebs4TabItems(session, inputId = "sidebarID", selected = 3)
})
}
shinyApp(ui, server)
I'm using a shinydashboard but when the title is too long it fails to wrap the lines. I have tried using <br/> to accomplish this, but it doesn't work even with HTML() around it in this context.
I know I can make the title space wider with titleWidth, but that does not look as good in many cases.
What would be the simplest way to achieve this?
Here's an example:
library(shiny)
library(shinydashboard)
## Only run this example in interactive R sessions
if (interactive()) {
header <- dashboardHeader(title = "This title is just way too long")
sidebar <- dashboardSidebar(
sidebarUserPanel("User Name",
subtitle = a(href = "#", icon("circle", class = "text-success"), "Online"),
# Image file should be in www/ subdir
image = "userimage.png"
),
sidebarSearchForm(label = "Enter a number", "searchText", "searchButton"),
sidebarMenu(
# Setting id makes input$tabs give the tabName of currently-selected tab
id = "tabs",
menuItem("Dashboard", tabName = "dashboard", icon = icon("dashboard")),
menuItem("Widgets", icon = icon("th"), tabName = "widgets", badgeLabel = "new",
badgeColor = "green"),
menuItem("Charts", icon = icon("bar-chart-o"),
menuSubItem("Sub-item 1", tabName = "subitem1"),
menuSubItem("Sub-item 2", tabName = "subitem2")
)
)
)
body <- dashboardBody(
tabItems(
tabItem("dashboard",
div(p("Dashboard tab content"))
),
tabItem("widgets",
"Widgets tab content"
),
tabItem("subitem1",
"Sub-item 1 tab content"
),
tabItem("subitem2",
"Sub-item 2 tab content"
)
)
)
shinyApp(
ui = dashboardPage(header, sidebar, body),
server = function(input, output) { }
)
}
The goal is to apply word-wrapping so that we can read the entire title (which says "This title is just way too long").
header <- dashboardHeader(title = h4(HTML("This title<br/>is just way too long")))
shinyApp(
ui = dashboardPage(header, sidebar, body),
server = function(input, output) { }
)