I have a shiny app with a sidebar menue and several different tabs. Within each tab, there is a lot of content that is supposed to be seen together, so the tabs are quite lengthy and navigating can be a pain because a lot of scrolling is needed. However, spliting the content into sub-tabs is not an option.
I have thus tried to implement "location markers" as fake sub-tabs to navigate through.
This works fine, except when you are on another tab and you want to switch directly to the bottom of another tab, i.e. from subtab_1_1 directly to subtab_2_2.
In that case, the tab switches over correctly to subtab_2_1 but the scrollposition() afterwards does not actually scroll the full 50000 pixels, but to the maximum distance of the active tab (i.e. the bottom of Tab 1).
As #YBS pointed out, one solution would be to add lines to each Tab so that they all share the same length. However, that would make using the scroll bar to scroll manualy very unintuitive, as some tabs would go on for much longer as the content of the tab.
Is there any way to circumvent this limitation of window.scrollTo()?
Here is a minimal working example:
library(shiny)
library(shinydashboard)
library(shinyjs)
ui <- dashboardPage(
title = "Title",
dashboardHeader(title = "Header", titleWidth = 350),
skin = "blue",
dashboardSidebar(
width = 350,
disable = FALSE,
sidebarMenu(
id = "tabs",
menuItem(
text = "Tab 1",
tabName = "Tab_1",
icon = icon("database"),
hidden(menuSubItem(
text = "Subtab 1.1",
tabName = "Subtab_1_1",
icon = icon("angle-right")
)),
menuSubItem(
text = "Proxy Subtab 1.1",
tabName = "Proxy_Subtab_1_1",
icon = icon("angle-right")
),
menuSubItem(
text = "Subtab 1.2",
tabName = "Subtab_1_2",
icon = icon("angle-right")
)
),
menuItem(
text = "Tab 2",
tabName = "Tab_2",
icon = icon("database"),
hidden(menuSubItem(
text = "Subtab 2.1",
tabName = "Subtab_2_1",
icon = icon("angle-right")
)),
menuSubItem(
text = "Proxy Subtab 2.1",
tabName = "Proxy_Subtab_2_1",
icon = icon("angle-right")
),
menuSubItem(
text = "Subtab 2.2",
tabName = "Subtab_2_2",
icon = icon("angle-right")
)
)
)
),
dashboardBody(
useShinyjs(),
extendShinyjs(
text = "shinyjs.scrollposition = function(y) {window.scrollTo(0, y)};",
functions = c("scrollposition")
),
tags$script(HTML("$('body').addClass('fixed');")),
tabItems(
tabItem(
tabName = "Subtab_1_1",
fluidPage(
h1("This is Subtab 1_1"),
HTML(rep("<br/><br/><br/>↓<br/>", 10)),
h1("This is supposed to be Subtab 1_2")
)
),
tabItem(
tabName = "Subtab_2_1",
fluidPage(
h1("This is Subtab 2_1"),
plotOutput("Plot_1"),
plotOutput("Plot_2"),
plotOutput("Plot_3"),
plotOutput("Plot_4"),
plotOutput("Plot_5"),
plotOutput("Plot_6"),
h1("This is supposed to be Subtab 2_2")
)
)
)
)
)
server <- function(input, output, session) {
observeEvent(input$tabs, {
if(sum(c("Proxy_Subtab_1_1", "Proxy_Subtab_2_1","Subtab_1_2", "Subtab_2_2") %in% input$tabs) > 0) {
updateTabsetPanel(session, "tabs", switch(input$tabs,
"Proxy_Subtab_1_1" = "Subtab_1_1",
"Proxy_Subtab_2_1" = "Subtab_2_1",
"Subtab_1_2" = "Subtab_1_1",
"Subtab_2_2" = "Subtab_2_1")
)
js$scrollposition(case_when(input$tabs == "Proxy_Subtab_1_1" ~ 0,
input$tabs == "Proxy_Subtab_2_1" ~ 0,
input$tabs == "Subtab_1_2" ~ 50000,
input$tabs == "Subtab_2_2" ~ 50000)
)
}
})
output$Plot_1 <- output$Plot_2 <- output$Plot3 <-
output$Plot_4 <- output$Plot_5 <- output$Plot6 <- renderPlot(
ggplot(data.frame(
x = c(1, 2, 3),
y = c(1, 2, 3),
labels = c(
"",
"Some plots",
""
)
)
) +
geom_text(aes(x = x, y = y, label = labels), size = 6)
)
}
shinyApp(ui = ui, server = server)
Related
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.
I have the shinydashboard below in which I have 3 tabPanels. In the 1st tabPanel "Resource Allocation" I want the left and right sidebar open by default. In the 2nd and 3rd tabpanels ("Time Series","Longitudinal View") I want only left sidebar and the right sidebar not just hidden but to not be able to open at all by pushing the "gears" icon above it which should be removed. And in the fourth panel "User Guide" I want no sidebar and no choise to open one of them at all.
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyjs)
shinyApp(
ui = dashboardPage(
options = list(sidebarExpandOnHover = TRUE),
header = dashboardHeader(
titleWidth = "0px"
),
sidebar = dashboardSidebar(minified = TRUE, collapsed = F),
body = dashboardBody(
useShinyjs(),#tags$head(tags$script(src="format_number.js")),
tags$script("document.getElementsByClassName('sidebar-toggle')[0].style.visibility = 'hidden';"),
tabsetPanel(
tabPanel("Resource Allocation"),
tabPanel("Time Series"),
tabPanel("Longitudinal View"),
tabPanel("User Guide")
)
),
controlbar = dashboardControlbar(collapsed = F),
title = "DashboardPage"
),
server = function(input, output) { }
)
I have a solution for the left sidebar. I am sure you can spend sometime and figure out the solution for the right sidebar. Please note that this requires some more work to fine tune to your needs. Try this
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyjs)
library(DT)
ui <- shinydashboardPlus::dashboardPage(
options = list(sidebarExpandOnHover = TRUE),
shinydashboardPlus::dashboardHeader(
#titleWidth = "0px"
),
shinydashboardPlus::dashboardSidebar( disable = TRUE ,
sidebarMenu(
selectInput(
"countries", label = "Select Countries",
choices = c("B", "C", "A"), selected = "A",
multiple = TRUE
))
),# minified = TRUE, collapsed = F),
controlbar = shinydashboardPlus::dashboardControlbar(id = "controlbar", collapsed = F,
skin = "dark",
controlbarMenu(
id = "menu",
controlbarItem(
"Tab 1",
"Welcome to tab 1"
),
controlbarItem(
"Tab 2",
"Welcome to tab 2"
)
)
),
shinydashboard::dashboardBody(
useShinyjs(),
tabsetPanel( id="tabset",
tabPanel("Resource Allocation", value="tab1", plotOutput("plot")),
tabPanel("Time Series", value="tab2", plotOutput("plot2")),
tabPanel("Longitudinal View", value="tab3", DTOutput("ir")),
tabPanel("User Guide", value="tab4", DTOutput("mt"))
)
),
# controlbar = dashboardControlbar(collapsed = F),
title = "DashboardPage"
)
server <- function(input, output) {
output$plot <- renderPlot(plot(cars))
output$plot2 <- renderPlot(plot(pressure))
output$mt <- renderDT(mtcars)
output$ir <- renderDT(iris)
observeEvent(input[["tabset"]], {
if(input[["tabset"]] == "tab4"){
addClass(selector = "body", class = "sidebar-collapse")
updateControlbar("controlbar")
}else{
removeClass(selector = "body", class = "sidebar-collapse")
}
})
}
shinyApp(ui, server)
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 am adding clickable icons in my shiny app to show a popup information box. Please see the following screenshot and code example. My strategy is to wrap my text and the code for actionLink in the HTML function. This works well. However, the size of the icon is determined by the size of the associated. I would like to know if it is possible to make all the icons the same size, such as the smallest one associated with the selectInput?
The documentation (https://shiny.rstudio.com/reference/shiny/1.0.1/icon.html) mentioned that it is to set "fa-3x" in the icon function to make the size to be 3 times as normal. But in my case the size would sill be determined by the associated text and each text has a different size. So I guess this strategy would not work. It would be great if anyone can share their ideas or suggestions.
# Load the packages
library(shiny)
library(shinydashboard)
library(shinyalert)
# User Interface
ui <- dashboardPage(
header = dashboardHeader(title = ""),
sidebar = dashboardSidebar(
sidebarMenu(
menuItem(
text = "Example",
tabName = "tab1"
)
)
),
body = dashboardBody(
# A call to use the shinyalert package
useShinyalert(),
tabItems(
tabItem(
tabName = "tab1",
h2(HTML("This is a title",
as.character(actionLink(inputId = "info1",
label = "",
icon = icon("info"))))),
fluidRow(
box(
title = HTML("This is the title of the box",
as.character(actionLink(inputId = "info2",
label = "",
icon = icon("info")))),
status = "primary", solidHeader = TRUE,
selectInput(inputId = "Select",
label = HTML("This is the title of the selectInput",
as.character(actionLink(inputId = "info3",
label = "",
icon = icon("info")))),
choices = 1:3)
)
)
)
)
)
)
server <- function(input, output, session){
observeEvent(input$info1, {
shinyalert(text = "Info 1", type = "info")
})
observeEvent(input$info2, {
shinyalert(text = "Info 2", type = "info")
})
observeEvent(input$info3, {
shinyalert(text = "Info 3", type = "info")
})
}
# Run the app
shinyApp(ui, server)
I'm not sure if I understand your question correctly but if you want the them all to have the same size you can adapt the font size for the icons like this:
# Load the packages
library(shiny)
library(shinydashboard)
library(shinyalert)
# User Interface
ui <- dashboardPage(
header = dashboardHeader(title = ""),
sidebar = dashboardSidebar(
sidebarMenu(
menuItem(
text = "Example",
tabName = "tab1"
)
)
),
body = dashboardBody(
# A call to use the shinyalert package
useShinyalert(),
tabItems(
tabItem(
tabName = "tab1",
h2(HTML("This is a title", "<font size='3'>",
as.character(actionLink(inputId = "info1",
label = "",
icon = icon("info"))), "</font>")),
fluidRow(
box(
title = HTML("This is the title of the box", "<font size='3'>",
as.character(actionLink(inputId = "info2",
label = "",
icon = icon("info"))), "</font>"),
status = "primary", solidHeader = TRUE,
selectInput(inputId = "Select",
label = HTML("This is the title of the selectInput", "<font size='3'>", as.character(actionLink(inputId = "info3",
label = "",
icon = icon("info"))), "</font>"
),
choices = 1:3)
)
)
)
)
)
)
server <- function(input, output, session){
observeEvent(input$info1, {
shinyalert(text = "Info 1", type = "info")
})
observeEvent(input$info2, {
shinyalert(text = "Info 2", type = "info")
})
observeEvent(input$info3, {
shinyalert(text = "Info 3", type = "info")
})
}
# Run the app
shinyApp(ui, server)
I would like to know why when a open a page (a1) in my shiny app and then I open another page (for example a2) then I can't come back to a1?
To reproduce my example please follow these steps:
click on a2
click on b1
click on c1
click on c8
click on c1 again
I would expect to see the content of c1 again ("example of text 2"), but clicking c1 again appears to do nothing.
library(shinydashboard)
library(shiny)
sidebar <- dashboardSidebar(
tags$head(tags$style(
HTML('.content-wrapper { height: 1500px !important;}')
)),
hr(),
sidebarMenu(
id = "tabs",
menuItem(
"a1",
tabName = "principal",
icon = icon("pagelines"),
selected = TRUE
),
menuItem(
"a2",
icon = icon("chart-bar"),
menuSubItem("b1", tabName = "identificacion", icon = icon("angle-right")),
menuSubItem("b2", tabName = "comunicacion", icon = icon("angle-right")),
menuSubItem("b3", tabName = "medicamentos", icon = icon("angle-right")),
menuSubItem("b4", tabName = "cirugias", icon = icon("angle-right")),
menuSubItem("b5", tabName = "infecciones", icon = icon("angle-right")),
menuSubItem("b6", tabName = "caidas", icon = icon("angle-right"))
),
menuItem("a3", tabName = "procesos", icon = icon("chart-bar")),
menuItem("a4", tabName = "tiempos", icon = icon("chart-bar")),
menuItem("a5", tabName = "manual", icon = icon("mortar-board")),
menuItem("a6", tabName = "acerca", icon = icon("question"))
),
width = 285,
hr(),
conditionalPanel("input.tabs=='identificacion'",
fluidRow(
column(1),
column(
10,
menuItem(
"c1",
tabName = "admision_iden",
icon = icon("chart-line"),
selected = FALSE
),
menuItem(
"c8",
tabName = "uci_iden",
icon = icon("chart-line"),
selected = FALSE
)
)
))
)
body <- dashboardBody(tabItems(
tabItem(tabName = "principal",
withMathJax(), ("example of text")),
tabItem(tabName = "admision_iden", titlePanel("example1"), "example of text 2"),
tabItem(tabName = "uci_iden", titlePanel("example 2"), "example of text 3")
))
ui <- dashboardPage(dashboardHeader(title = "Indic", titleWidth = 285),
sidebar,
body)
server <- function(input, output) {}
runApp(list(ui = ui, server = server))
Your additional menus are rendered outside sidebarMenu, so when selected, their ids are not recorded in the input variable input$tabs (which is what the app is monitoring). Including the menus inside sidebarMenu allows the tabItems to track the menuItems, but also breaks the reactivity, i.e., when input$tabs == 'uci_iden' the additional menus disappear (because input$tabs != 'identificacion').
One way to achieve the behavior you want (possibly not the best way) is to render the additional menus through the server using renderUI. Note that we still have to include the additional menus inside sidebarMenu and monitor them through input$tabs. To make sure they stick around when input$tabs != 'identificacion', we can include their ids in the condition. When neither of identification, uni_iden or admision_iden are selected, we render an empty div.
Updated code:
library(shinydashboard)
library(shiny)
sidebar <- dashboardSidebar(
tags$head(tags$style(
HTML('.content-wrapper { height: 1500px !important;}')
)),
hr(),
sidebarMenu(
id = "tabs",
menuItem(
"a1",
tabName = "principal",
icon = icon("pagelines"),
selected = TRUE
),
menuItem(
"a2",
icon = icon("chart-bar"),
menuSubItem("b1", tabName = "identificacion", icon = icon("angle-right")),
menuSubItem("b2", tabName = "comunicacion", icon = icon("angle-right")),
menuSubItem("b3", tabName = "medicamentos", icon = icon("angle-right")),
menuSubItem("b4", tabName = "cirugias", icon = icon("angle-right")),
menuSubItem("b5", tabName = "infecciones", icon = icon("angle-right")),
menuSubItem("b6", tabName = "caidas", icon = icon("angle-right"))
),
menuItem("a3", tabName = "procesos", icon = icon("chart-bar")),
menuItem("a4", tabName = "tiempos", icon = icon("chart-bar")),
menuItem("a5", tabName = "manual", icon = icon("mortar-board")),
menuItem("a6", tabName = "acerca", icon = icon("question")),
width = 285,
hr(),
uiOutput("more_menus")
)
)
body <- dashboardBody(tabItems(
tabItem(tabName = "principal",
withMathJax(), ("example of text")),
tabItem(tabName = "admision_iden", titlePanel("example1"), "example of text 2"),
tabItem(tabName = "uci_iden", titlePanel("example 2"), "example of text 3")
))
ui <- dashboardPage(dashboardHeader(title = "Indic", titleWidth = 285),
sidebar,
body)
server <- function(input, output) {
make_menus <- reactive({
if (req(input$tabs) %in% c("identificacion", "admision_iden", "uci_iden")) {
fluidRow(column(1),
column(
10,
menuItem(
"c1",
tabName = "admision_iden",
icon = icon("chart-line"),
selected = FALSE
),
menuItem(
"c8",
tabName = "uci_iden",
icon = icon("chart-line"),
selected = FALSE
)
))
} else {
div()
}
})
output$more_menus <- renderUI({ make_menus() })
}
runApp(list(ui = ui, server = server))