`tabitem` Content Of Conditional `menuitem` Is Showing Only Once in Shiny - r

I want a navigation menuItem in my shinydashboard to be conditional and shown depending on a condition evaluated in server.R.
To this end, I have a conditionalPanel containing a menuItem defined beside a regular sidebarMenu in ui.R (I use shinymanager to authenticate users):
sidebar <- dashboardSidebar(
width=280,
sidebarMenu(id = "sidebarmenu",
menuItem(...),
menuItem(...,
menuSubItem(...),
menuSubItem(...)
)
),
conditionalPanel(condition = "output.x === 1",
menuItem("title", tabName="tabname")
)
)
body <- dashboardBody(
tabItems(
tabItem(tabName = "id",
fluidPage(
titlePanel("Hello World")
)),
tabItem(tabName="tabname",
titlePanel("mytitle"),
fluidPage(
dataTableOutput(outputId = "table")
)
)
)
ui <- dashboardPage(
dashboardHeader(title = "Hello App", titleWidth=280),
sidebar,
body
)
ui <- secure_app(ui)
In server.R, I switch output.x depending on the logon details of the logged user:
server <- function(input, output, session) {
# login logic: call the server part, check_credentials returns a function to
# authenticate users
res_auth = secure_server(
check_credentials = check_credentials
)
# Define the logon details with a reactive variable
auth_output <- reactive({
reactiveValuesToList(res_auth)
})
output$x = reactive({
auth_output()$role
})
# Generate a data table from the DB to show conditionally
conn = ...
data = load_data(conn, ...)
disconnect(conn)
output$table = dt_render({data})
# All output variables that need to be transferred to the UI should have
# suspendWhenHidden = FALSE:
outputOptions(output, "x", suspendWhenHidden = FALSE)
The problem: the conditional table is shown only once, whenever I want. After this one time, once I navigate away from it, clicking on the conditional menuItem shows no content. The menuItem still appears, which means that output.x === 1 is evaluated properly, but its contents, i.e. the subsequent tabItem, remains hidden.
I have tried isolate to assign output.x, and even fixed it at 1 to no avail. Any leads?

Since the conditionalPanel cannot be put inside the default sidebarMenu, it must be in its stand-alone conditional sidebarMenu, so I must define two sidebarMenus under dashboardSidebar in this case. The following modification solves the problem:
sidebar <- dashboardSidebar(
width=280,
sidebarMenu(id = "sidebarmenu",
menuItem(...),
menuItem(...,
menuSubItem(...),
menuSubItem(...)
)
),
sidebarMenu(id = "conditional_sidebarmenu",
conditionalPanel(condition = "output.x === 1",
menuItem("title", tabName="tabname")
)
)

Related

shiny:renderUI and shiny:uioutput to produce a new menuItem and a new output, but the tabItem not showing

I want to create a new menuItem and a new plotOutput based on an actionButton, and the newly rendered plotOutput will display a plot based on the submitted value. I have successfully generated the menuItem and the numericInput widget with submitted value, but the plot and the corresponding tabItem is not showing.
Here is the workflow:
submit -> render a menuItem with a input object and a plotOutput -> the plotOutput will be display based on the rendered input object
The second procedure is successful but the rest of that is not working, the code is listed below:
library(shiny)
library(shinydashboard)
## ============================================ Define ui ==================================================
header1 <- dashboardHeader(
title = "My Dynamic Menu"
) #dashboardHeader
# DYNAMIC UI
sidebar1 <- dashboardSidebar(
sidebarMenu(
menuItem('aa',tabName = 'aa')
) ,
sidebarMenuOutput('bb')
) #dashboardSidebar
#
body1 <- dashboardBody(
tabItems(
tabItem(tabName = 'aa',
numericInput('num_input', 'number', value = 5),
actionButton('submit','Submit')),
tabItem(tabName = "main", uiOutput('eee')) # put a tabItem here
) #tabItems
) #dashboardBody
ui <- dashboardPage(header1, sidebar1, body1)
server <- function(input, output, session) {
dt<-eventReactive(input$submit, {
input$num_input * 5
})
observeEvent(input$submit, {
output$bb<-renderMenu({
sidebarMenu(
menuItem("Main", tabName = "main",
numericInput('ddd', 'input value', value = dt()),
numericInput('ggg', 'another input', value=dt()+5))
)
})
output$eee<-renderUI({
fluidRow(
p('hello'),
plotOutput('fff')
)
})
})
observeEvent({
input$ddd
input$ggg
},{
output$fff<-renderPlot({
plot(1:input$ddd, main=as.character(input$ggg))
})
})
} #server
## ============================================ Run application ============================================
shinyApp(ui, server)
Many thanks if this issue is addressed.
to be linked with a tabItem, menuItem should only contain menuSubItem. If you remove your two numeric inputs (ddd and ggg) then the main page is activated when you click on the newly created main menuitem. Mixing navigation and inputs is not a good idea.
You'd better move you inputs to your main tabItem or put them in a separate panel (not in the menu) in the sidebar.
added a uiOutput for inputs in ui
# DYNAMIC UI
sidebar1 <- dashboardSidebar(
sidebarMenu(
menuItem('aa',tabName = 'aa')
) ,
sidebarMenuOutput('bb'),
uiOutput("inputs")
) #dashboardSidebar
added a renderUI for this output in server
output$inputs <- renderUI(tagList(
numericInput('ddd', 'input value', value = dt()),
numericInput('ggg', 'another input', value=dt()+5)
))
and removed inputs from your renderMenu
output$bb<-renderMenu({
message("bb")
sidebarMenu(
menuItem("Main", tabName = "main")
)
})
You have to navigate manually to the main page though to display the plot

Shiny how to refresh data loaded before server function

I need to develope a shiny interface with many csv loaded in it. Based on my past experience with shiny, I prefer to import this data before the server function, in this way I hope that each session will run faster. The app will be restarted each morning, so data will be daily refreshed.
The problem is that I need to consider an extra refresh during the day, performed manualy with a button that source an external updating script.
I can't (but I hope that is possibele), refresh the data loaded at the start of the app. Below my (dummy) code:
server:
library(shinydashboard)
library(plotly)
library(data.table)
library(dplyr)
path1<-"C:/Users/.../DATA/"
path2<-"C:/Users/../DATA/csv/"
##load dataset at first start
table<-fread(file=paste0(path2,"main.csv"),data.table=FALSE))
shinyServer(function(input, output,session) {
##### refresh data with button####
observeEvent(input$refresh_data,{
source(paste0(path1,"any_script.r"),local = FALSE)
table<<-fread(file=paste0(path2,"main.csv"))
})
#####...ui####
table_r<-reactive({
##obs populate the input for choosing rows to be plotted
obs<-rev(unique(table$anycolumn))
curve_sint<-list(
lotti=obs,
data=obs
)
})
output$obs_ui<-renderUI({
selectInput("input_obs","Please choose the batch:",
choices =table()$obs ,multiple = T)
})
output$plot<-renderPlotly({
table_r()$data%>%
filter(anycolumn==input$input_obs)%>%
plot_ly(
x=~x,
y=~y,
color=~anycolumn,
type="scatter"
)
})
})
ui:
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(
title = "shiny"
),
dashboardSidebar(
width=250,
sidebarMenu(
menuItem(
"plot data"
tabName = "clhc",
icon = NULL
),
menuItem(
"Update data",
icon=icon("gear"),
tabName="update_data"
)
)
),
dashboardBody(
tabItems(
tabItem(
tabName = "clhc",
fluidRow(
column(width=3,
uiOutput("obs_ui")
),
),
fluidRow(
column(
width=12,
fluidRow(
plotlyOutput("plot")
)
)
)
),
tabItem(
tabName = "update_data",
fluidRow(
box(
width=12,
title="Sint HC",
actionButton("refresh_sint_hc","Refresh", icon=icon("refresh"))
)
)
)
)
)
)
I'm sure that the script inside observeevent works fine, because if I put a print(nrow(table)) after the fread I can see that table is correctly refreshed. I can't understand where the new data is stored because from the plot panel is stil available the old data before the update.
Is my attempt completley wrong?
Using <<- will make table accessible globally, and after terminating your shiny app, but you need it to be reactive. Here is a brief example on using a reactiveVal (setting to table1 as default) that gets modified when the actionButton is selected and a new data file is read.
library(shiny)
library(data.table)
table1 <- fread(file = 'atest1.csv')
ui <- fluidPage(
verbatimTextOutput("text"),
actionButton("refresh", "Refresh")
)
server <- function(input, output, session) {
rv <- reactiveVal(table1)
output$text <- renderText({
names(rv())
})
observeEvent(input$refresh, {
print("Refresh")
table1 <<- fread(file = 'atest2.csv')
rv(table1)
})
}
shinyApp(ui, server)

R Shiny reactive triggered by navigating to particular tabPanel in navbarPage

writing with a shiny question. I have a navbarPage, id = "navbar", and in the navbarMenu user can select one among several tabPanels. Each tabPanel is assigned a value (value = 1, value = 2, etc). So input$navbar is reactive value with the value of the selected tabPanel.
I have a reactive expression defined which reacts to the changing of the tabPanel (reacts based on input$navbar). What I actually want is for this to react to navigating to a particular tabPanel, but not navigating away from that tabPanel. So, when input$navbar changes from 1 to 2 I want a reaction, but when changing from 2 to 1 no reaction. How can I achieve this?
Here is relevant snippet of my code, I don't think I need a full reproducible example for this but let me know if I'm wrong.
#ui snippet
navbarPage(id = "navbar",
navbarMenu(title = "Title",
tabPanel(title = "tp1", value = 1),
tabPanel(title = "tp2", value = 2),
#more tabPanels and ui stuff...
#server snippet
rctvfx <- reactive({
#want this to react only when input$navbar changes from ==1 to ==2
input$navbar
isolate({
#do stuff
})
})
You can use an if statement. This makes sure the code only runs if the user navigated to the corresponding tab.
library(shiny)
shinyApp(
ui = navbarPage(
"App Title",
id = "navbar",
tabPanel("Plot"),
navbarMenu(
"More",
tabPanel("Summary"),
"----",
"Section header",
tabPanel("Table")
)
),
server = function(input, output){
observe({
if (req(input$navbar) == "Table")
message("Table has been selected")
if (req(input$navbar) == "Plot")
message("Plot has been selected")
})
}
)
I would recomment do use observe rather than reactive to make sure everything runs even if all observers for the reactive are idle.
Another example of the same answer as above
library(shiny)
shinyApp(
ui = navbarPage(
"App Title",
id = "navbar",
tabPanel("Plot"),
navbarMenu(
"More",
tabPanel("Summary"),
tabPanel("Table"),
mainPanel(dataTableOutput("d"))
)
),
server = function(input, output){
output$d = renderDataTable({
if ((input$navbar) == "Table") {
head(mtcars)
} else {
((input$navbar) == "Plot")
head(iris)
}
})
}
)

Shiny module access output outside namespace

I need my Shiny module to hide/show a div outside of the namespace. I tried passing the div id to the module server function and using shinyjs to show/hide it but that is not working. I'm not getting an error, it just doesn't show/hide the div.
I know the Shiny module documentation says modules cannot access outputs outside the namespace. The docs do, though, give a way for the module to access inputs outside the namespace using reactives.
Does anyone know if there is a way for a Shiny module to access an output outside the namespace?
Here is what I'm trying to do:
### ui.R ###
header <- dashboardHeader(
title = a(href = 'http://google.com')
)
dashboardPage(
skin = 'black',
header,
dashboardSidebar(
sidebarMenu( id='tabs',
menuItem('Edit Existing Client', tabName = 'client-info')
)),
dashboardBody(
useShinyjs(),
fluidRow(
tabItems(
tabItem(tabName = "client-info",
div(selectClientModuleUI("clientinfons")),
div(id='editclientinfo', uiOutput('editclientstuff'))
)
)
)
)
)
### server.R ###
shinyServer(function(session,input, output) {
output$editclientstuff <- renderUI({
div(
fluidRow(
column(6,
textInput('editname', "Display name", value ='Testing name')
),
column(6,
numericInput('editastart','Start', value ='3')
)
)
)
})
callModule(selectClientModule, 'clientinfons', 'editclientinfo')
shinyjs::hide(id='editclientstuff')
})
### in global.R ###
selectClientModuleUI <- function(id){
ns <- NS(id)
clientlist = c(0, 1, 2)
names(clientlist) = c('Choose client', 'Fred', 'Kim')
div(
selectInput(ns('selectclient'), 'Select client to edit', choices = clientlist, selected = NULL, multiple = FALSE)
)
}
selectClientModule <- function(input, output, session, divtoshow = ''){
observeEvent(input$selectclient, {
if (!is.null(input$selectclient) && input$selectclient > 0){
print(paste0("showing ", divtoshow))
shinyjs::show(divtoshow)
}
})
}
That is possible by giving the value as a reactive (not as the value of the reactive) to the module. You can change the reactive Value in the module and return the reactive from the Module to the app (note, return the reactive itself, not its value). The following app switches the 'divtoshow' in the main app from inside the module. If nothing is selected, it's hidden, otherwise it's shown (note, I adjusted you code a little so it's working as a stand-alone app):
library(shinydashboard)
library(shinyjs)
# Module
selectClientModuleUI <- function(id){
ns <- NS(id)
clientlist = c(0, 1, 2)
names(clientlist) = c('Choose client', 'Fred', 'Kim')
div(
selectInput(ns('selectclient'), 'Select client to edit', choices = clientlist, selected = NULL, multiple = FALSE)
)
}
selectClientModule <- function(input, output, session, divtoshow){
observeEvent(input$selectclient, {
if (input$selectclient > 0){
print(paste0("showing editclientinfo"))
divtoshow("editclientinfo") # set the div to show to "editclientinfo", this will be visible outside the module
}else{
divtoshow("") # set the div to show to "", if nothing was chosen
}
})
# return the div to show as reactive to the main app
return(divtoshow)
}
# Main App
ui <- shinyUI(
dashboardPage(
skin = 'black',
dashboardHeader(
title = a(href = 'http://google.com')
),
dashboardSidebar(
sidebarMenu( id='tabs',
menuItem('Edit Existing Client', tabName = 'client-info')
)),
dashboardBody(
useShinyjs(),
fluidRow(
tabItems(
tabItem(tabName = "client-info",
div(selectClientModuleUI("clientinfons")),
div(id='editclientinfo', uiOutput('editclientstuff'))
)
)
)
)
))
server <- shinyServer(function(session,input, output) {
output$editclientstuff <- renderUI({
div(
fluidRow(
column(6,
textInput('editname', "Display name", value ='Testing name')
),
column(6,
numericInput('editastart','Start', value ='3')
)
)
)
})
# store the div to show in a reactive
divtoshow <- reactiveVal('')
# divtoshow can be changed in side this module, so it's a return value
divtoshow <- callModule(selectClientModule, 'clientinfons', divtoshow)
# observe the value of divtoshow and toggle the corresponding div
observeEvent(divtoshow(), {
if(divtoshow() == "editclientinfo"){
shinyjs::show("editclientinfo")
}else{
shinyjs::hide("editclientinfo")
}
})
})
shinyApp(ui, server)

Shiny Dashboard - display a dedicated "loading.." page until initial loading of the data is done

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(
....

Resources