I am having a following problem:
I want a sidebar change when I switch between tabItem(s).
dashboardPage(
dasboardHeader(title = ""),
dashboardSidebar(
sidebarMenu(
menuItem("1", tabName = "1"),
menuItem("2", tabName = "2")
),
#I want this to be displayed when menuItem "1" is clicked
tabsetPanel(
tabPanel("t1", val="t1",
.... some inputs),
tabPanel("t2", val="t2",
.... some inputs)
),
# This to be displayed when menuItem "2" is clicked
selectInput("s1", label="Select"....),
selectInput("s2", label="Select2"...)
)
dashboardBody(
tabItem(tabName="1",
.......
),
tabItem(tabName="2",
........
)
)
)
I have the dashboardBody changing when switching between the tabs but don't know how to change the dashboardSidebar values. Tried this inside the dashboardSidebar:
conditionalPanel(
condition="tabName='1'",
#displaying first version of DashboardSidebar
),
conditionalPanel(
condition="tabName='2'",
#displaying second version of DashboardSidebar
)
But it didn't work for me.
Any ideas?
Thank you for your help.
First of all, you have to add sidebarMenu an ID which will be used for a Shiny input value, and it will report which tab is selected.
After that, add uiOutput to dashboardSidebar function.
uiOutput is going to receive, depending on a selected tab, either tabsetPanel or two selectInputs.
Finally, within renderUI, which you define on the server side, you just need to create conditional statements. That's the easy part.
The tricky part is that you have to wrap selectInputs into a list - otherwise only the second widget would be sent to the UI. Even more tricky part is that you have to specify the ID of tabsetPanel when you want to send it to the UI via renderUI. (That's very tricky because normally you don't have to specify its ID!)
Full example:
library(shiny)
library(shinydashboard)
rm(ui)
rm(server)
ui <- dashboardPage(
dashboardHeader(title = ""),
dashboardSidebar(
# added ID which will be used for a Shiny input value,
# and it will report which tab is selected.
sidebarMenu(id = "tab",
menuItem("1", tabName = "1"),
menuItem("2", tabName = "2")
),
uiOutput("out1")
),
dashboardBody(
tabItem(tabName = "1"),
tabItem(tabName = "2")
)
)
server <- function(input, output) {
output$out1 <- renderUI({
if (input$tab == "1") {
dyn_ui <- tabsetPanel(id = "tabset_id", selected = "t1",
tabPanel("t1", value = "t1"),
tabPanel("t2", value = "t2"))
}
if (input$tab == "2") {
dyn_ui <- list(selectInput("s1", label = "Select", choices = letters[1:3]),
selectInput("s2", label = "Select2", choices = letters[4:6]))
}
return(dyn_ui)
})
}
shinyApp(ui, server)
Related
I would like to have two instances of an input controller in my Shiny app, but I think that what I have to do instead is to have two inputs and update the value of each whenever the other changes. This way, they will appear to the user to be the same controls despite the fact that they have different IDs.
I anticipate being told to not do what I am trying to do, but the use case is that I have many tabs in a dashboardPage(), and only two of them share controls. Thus, putting the controls for those two pages in the sidebar would be confusing to the user.
I made a simple, working example of how to do this (using a dashboard to make it more clear why I want to do this) based on a closely-related question that was answered by convincing the asker to do something else (which worked in their case but not in mine). The app works fine except that as it gets more and more complex, the calculations take long enough sometimes that I can change one input and then change the other before the Shiny server has had time to update the values. This results in infinite feedback (input 1 updates to match input 2 while input 2 is updating to match input 1, and then this repeats for as long as I care to watch).
library(shiny)
library(shinydashboard)
ui = dashboardPage(
dashboardHeader(title = "Example"),
dashboardSidebar(
sidebarMenu(
menuItem("Tab 1", tabName = "tab1", icon = icon("chart-line")),
menuItem("Tab 2", tabName = "tab2", icon = icon("chart-line")),
menuItem("Other Tab", tabName = "tab3", icon = icon("project-diagram"))
)
),
dashboardBody(
tabItems(
# First tab content
tabItem(tabName = "tab1",
# Input first number
numericInput("input1", label = "Input 1", value = 1, min=1, step=1)
),
# Second tab content
tabItem(tabName = "tab2",
# Input second number
numericInput("input2", label = "Input 2", value = 1, min=1, step=1)
),
# Third tab content
tabItem(tabName = "tab3", "Unrelated content")
)
)
)
server = function(input, output, session) {
# Update inputs to match each other
observeEvent(input$input1, {
updateSelectInput(session = session,
inputId = "input2",
selected = input$input1)})
observeEvent(input$input2, {
updateSelectInput(session = session,
inputId = "input1",
selected = input$input2)})
}
shinyApp(ui = ui, server = server)
The question: what other ways are there to have separate pages with matching controls that control both pages but without having to put those controls on every page? Sub-question: is any of these methods going to avoid the infinite loop problem? Corollary: I saw an article that I think was rendering UI pages from auxiliary scripts and passing the input arguments to the URLs for those scripts, and that seemed like a great strategy, but I cannot find the article now and am struggling to figure it out on my own.
It is much simpler in fact. Instead of observing the numeric inputs, you can observe what tab is selected, and update a particular numericInput when the user arrives at that tab. So all we need is to provide an id for the sidebarMenu (id = "tabs", ...) and to observe the contents of this input variable:
observe({
if (req(input$tabs) == "tab2") {
updateSelectInput(...)
}
})
Changing input values with keyboard:
Changing input values with mouse clicking on up arrow:
Changing to tab2 while tab1 is rendering though the list of clicks:
Updated code:
library(shiny)
library(shinydashboard)
ui = dashboardPage(
dashboardHeader(title = "Example"),
dashboardSidebar(
sidebarMenu(id = "tabs",
menuItem("Tab 1", tabName = "tab1", icon = icon("chart-line")),
menuItem("Tab 2", tabName = "tab2", icon = icon("chart-line")),
menuItem("Other Tab", tabName = "tab3", icon = icon("project-diagram"))
)
),
dashboardBody(
tabItems(
# First tab content
tabItem(tabName = "tab1",
# Input first number
numericInput("input1", label = "Input 1", value = 1000, min=1, step=1),
plotOutput("plot1")
),
# Second tab content
tabItem(tabName = "tab2",
# Input second number
numericInput("input2", label = "Input 2", value = 1000, min=1, step=1),
plotOutput("plot2")
),
# Third tab content
tabItem(tabName = "tab3", "Unrelated content")
)
)
)
server = function(input, output, session) {
# some (not so) long computation
long_comp1 <- reactive({
x <- sample(input$input1, size=10000000, replace = TRUE)
y <- sample(input$input1, size=10000000, replace = TRUE)
m <- matrix(x, nrow = 500, ncol=200)
n <- matrix(y, nrow = 200, ncol=500)
p <- n %*% m
p
})
output$plot1 <- renderPlot({
hist(long_comp1(), main = paste("input1 is", input$input1))
})
# some (not so) long computation
long_comp2 <- reactive({
x <- sample(input$input2, size=10000000, replace = TRUE)
y <- sample(input$input2, size=10000000, replace = TRUE)
m <- matrix(x, nrow = 500, ncol=200)
n <- matrix(y, nrow = 200, ncol=500)
p <- n %*% m
p
})
output$plot2 <- renderPlot({
hist(long_comp2(), main = paste("input2 is", input$input2))
})
# Update inputs to match each other
observe({
if (req(input$tabs) == "tab2") {
updateSelectInput(session = session,
inputId = "input2",
selected = input$input1)
}
})
observe({
if (req(input$tabs) == "tab1") {
updateSelectInput(session = session,
inputId = "input1",
selected = input$input2)
}
})
}
shinyApp(ui = ui, server = server)
I am creating an app that allows the user to multi-select a list of projects. For each project selected, I would like to have a dateInput field to appear beside the project name (i.e. a/b/c).
Currently, I can display dateInput field for any one of the projects, but not multiple (shown below)
Once multiple dateInputs display beside project list as shown below: I wonder how the dateInput fields can line up nicely because they seem to take up more space than the checkbox list, like this:
ui <- dashboardPage(
dashboardHeader(
title = ""
),
dashboardSidebar(
sidebarMenu(
menuItem("Projects", tabName = "tab1")
)
),
dashboardBody(
tabItems(
tabItem(tabName = "tab1",
fluidRow(h2("Projects"),
column(6,
checkboxGroupInput("Projects", "",
c("a", "b", "c")
)
),
column(4,
conditionalPanel(condition = "input.Projects == 'a' ||
input.Projects == 'b' || input.Projects
== 'c'", dateInput("proj_date", ""))
)
)
)
)
)
)
server <- function(input, output) {
}
shinyApp(ui, server)
I understand I need to put in javascript in the conditionalPanel's condition argument. Please advise how I should correct the code and have one or more dateInput fields displayed for any and all selected projects.
Try uiOutput:
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(
sidebarMenu(
menuItem("Projects", tabName = "tab1")
)
),
dashboardBody(
tabItems(
tabItem(tabName = "tab1",
fluidRow(h2("Projects"),
column(6, checkboxGroupInput("Projects", "", c("a", "b", "c"))),
column(4, uiOutput('dates'))))
)
)
)
server <- function(input, output) {
output$dates <- renderUI({
lapply(input$Projects, function(project) {
div(dateInput(paste0("proj_date_",project), paste("Project", project, "date")))
})
})
}
shinyApp(ui, server)
My aim is to build menu which content is dynamically generated and user sees only menuItems/menuSubItems starting from the top to the selected one ("next" button will be used to reach next not yet displayed menuItems/menuSubItems; in case of selection of any available menuItems/menuSubItems menu should be regenerated so the last available menuItems/menuSubItems is selected one). So I will have to play with selected and startExpanded arguments, but this is not the part of this question.
The issue I faced is mutual connection of renderMenu and observeEvent (that checks selected menuItem/menuSubItem). Here is the code:
library(shiny)
library(shinydashboard)
menu_generator <- function(selected = NULL, expanded = NULL){
print("menu_generator")
output <- sidebarMenu(
menuItem("Charts1", icon = icon("bar-chart-o"),
menuSubItem("AAdashboard", tabName = "AAdashboard"),
menuSubItem("BBdashboard", tabName = "BBdashboard")
),
menuItem("Charts2", icon = icon("bar-chart-o"),
menuSubItem("DDdashboard", tabName = "DDdashboard"),
menuSubItem("EWidgets", tabName = "EWidgets")
)
)
return(output)
}
ui <- dashboardPage(
dashboardHeader(title = "Test"),
dashboardSidebar(sidebarMenu(
sidebarMenuOutput("menu_output")
)),
dashboardBody(
tabItems(
tabItem(tabName = "AAdashboard",
h2("ADashboard tab content")
),
tabItem(tabName = "BBdashboard",
h2("BWidgets tab content")
),
tabItem(tabName = "DDdashboard",
h2("DWidgets tab content")
),
tabItem(tabName = "EWidgets",
h2("EWidgets tab content")
)
))
)
server <- function(input, output) {
output$menu_output <- renderMenu({
print("output$menu_output")
sidebarMenu(menu_generator(),
id = "my_menu")
})
observeEvent(input$my_menu, {
print("observer")
print(input$my_menu)
print(input$sidebarItemExpanded)
output$menu_output <- renderMenu({
sidebarMenu(menu_generator(),
id = "my_menu")
})
})
}
shinyApp(ui, server)
Scenario: expand Charts2 then click "DDdashboard".
In the console:
[1] "observer"
[1] "DDdashboard"
[1] "Charts2"
[1] "menu_generator"
[1] "observer"
[1] "AAdashboard"
NULL
[1] "menu_generator"
"observer" is called twice (as consequence "menu_generator" also), so this creates unexpected behaviour. My understanding is that reason of that is nature of renderMenu. The question is - how to prevent Shiny from calling "observer" in this case twice? Also notice that in the second call input$my_menu = "AAdashboard".
observeEvent(input$sidebarItemExpanded == 'DDdashboard',{
print("DDdashboard_selected")
})
I use shiny with shinydashboard. I have one tabbox with two tabPanels. Then there is another box which should display either textOutput("a") if tab1 in tabbox is selected or textOutput("b") if tab2 is selected.
I provide whole code for reproducibility but watch out for comments which show where the important part is.
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
skin = "red",
dashboardHeader(title = "lalala", titleWidth = 450),
sidebar <- dashboardSidebar(width = 400,
sidebarMenu(
menuItem(
text = strong("First tab"),
tabName = "first",
icon = icon("dashboard")
)
)),
body <- dashboardBody(fluidRow(
tabBox(
title = "First tabBox",
id = "tabset1",
height = "250px",
############## based on which of this tab is selected
tabPanel("Tab1", "First tab content"),
tabPanel("Tab2", "Tab content 2")
),
box(
title = "Selection criteria for chart",
height = "700px",
width = 4,
solidHeader = TRUE,
status = "danger",
############## I want in this box to display either textouput "a" or "b"
textOutput("a")
)
))
)
server <- function(input, output) {
output$a <- renderText(a <- "ahoj")
output$b <- renderText(b <- "cau")
}
input$tabset1 returns the id of the currently selected tab (so either Tab1 or Tab2). Then you can use an if/else statement to print the content you like depending on this return value.
I have initial loading of data from the DB in the server.R which takes a few seconds. Until this is done, the page displayed is distorted (wrong data in selection box, and weird placing of the boxes, see below).
I want to display a different page (or at least different content in my first-displayed tab) until the data is completely loaded.
I thought about doing some kind of conditionalPanel using a condition based on a dedicated global variable (initial_loading_done), but wherever I tried placing the conditionalPanel it didn't work.
This is the structure of my UI.R:
shinyUI(
dashboardPage(
dashboardHeader(title = "Title"),
dashboardSidebar(
sidebarMenu(
menuItem("Tab1", tabName = "Tab1",icon = icon("dashboard")),
menuItem("Tab2", tabName = "Tab2", icon = icon("bar-chart-o"))
)
),
dashboardBody(
includeCSS("custom_css.css"),
tabItems(
tabItem(tabName = "Tab1",
fluidRow(<content>),
mainPanel(
fluidRow(<content>)
)
),
tabItem(tabName = "Tab2",
fluidRow(<content>),
mainPanel(
dataTableOutput('my_data_table')
)
)
)
)
)
)
Here's a very simple example using shinyjs package
The idea is to create the loading "page" and the content "page" under different IDs, have the content page initially hidden, and use show() and hide() after the app is ready
library(shiny)
library(shinyjs)
load_data <- function() {
Sys.sleep(2)
hide("loading_page")
show("main_content")
}
ui <- fluidPage(
useShinyjs(),
div(
id = "loading_page",
h1("Loading...")
),
hidden(
div(
id = "main_content",
"Data loaded, content goes here"
)
)
)
server <- function(input, output, session) {
load_data()
}
shinyApp(ui = ui, server = server)
In server I like to use reactiveValues() to store a setupComplete condition. Then, when the data is loaded my setupComplete is set to TRUE.
In the ui we can then assess this setupComplete condition in a conditionalPanel, and only display the content (in my example the three box() widgets).
Here's a working example
## app.R ##
library(shiny)
library(shinydashboard)
library(shinyjs)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
actionButton(inputId = "btn_data", label = "Download"),
conditionalPanel(condition = "output.setupComplete",
box( title = "box1" ),
box( title = "box2" ),
box( title = "boc3" )
),
conditionalPanel(condition = "!output.setupComplete",
box( title = "loading"))
)
)
server <- function(input, output) {
rv <- reactiveValues()
rv$setupComplete <- FALSE
## simulate data load
observe({
if(input$btn_data){
df <- data.frame(id = seq(1,200),
val = rnorm(200, 0, 1))
## Simulate the data load
Sys.sleep(5)
## set my condition to TRUE
rv$setupComplete <- TRUE
}
## the conditional panel reads this output
output$setupComplete <- reactive({
return(rv$setupComplete)
})
outputOptions(output, 'setupComplete', suspendWhenHidden=FALSE)
})
}
shinyApp(ui, server)
The code
hidden(
div(
id = "main_content",
"Data loaded, content goes here"
)
doesn't work with tabsetPanel. But if you move the id to the div level it works beautifully. Thanks to shinyjs author Dean Attali for this tip. https://stackoverflow.com/users/4432127/keshete
hidden(
div(id = "mainTabsetPanel",
tabsetPanel(
....