I am using the shiny dashboard template to generate my web UI.
I'd like to dynamically generate an infobox when a computation is completed with a link directed to one of the tabItems in dashboardBody.
For example,
I can put this in my tabItem1 output,
renderInfoBox({
infoBox("Completed",
a("Computation Completed", href="#tabItem2"),
icon = icon("thumbs-o-up"), color = "green"
)
})
But the problem is that when I click the link, it does nothing. I would like it jumps to tabItem2. The link href seems valid when I hover on it.
Thanks!
Update:
Other than using Javascripts, looks like using actionLink and updateTabItems functions in shinydashboard package will work as well.
I apologize for the lengthy code sample, but I had to copy an example with tabItems from the shinydashboard homepage.
Your approach has only few problems. First, if you would inspect the menuItems, you'd see that the actual tab's id is not tabItem2, but shiny-tab-tabItem2. This, plus the extra attribute data-toggle="tab" within the a tag would suffice to open the desired tab. Snippet:
a("Computation Completed", href="#shiny-tab-tabItem2", "data-toggle" = "tab")
But, this has its limits. First and most obvious, the state of the menuItem in the sidebar is not set to active. This looks very odd and one might not be convinced, that one has been moved to another tab.
Second, and less obvious, if you listen to tab changes (on the server side), you will not get information about this tab switch. Those are triggered by the menuItem being clicked, and the tab itself will not report if it is visible or hidden.
So, my approach will be to simulate that the corresponding menuItem is clicked, and thus, all the above problems are solved.
Code example:
library(shiny)
library(shinydashboard)
ui <- shinyUI(
dashboardPage(
dashboardHeader(title = "Some Header"),
dashboardSidebar(
sidebarMenu(
menuItem("Computations", tabName = "tabItem1", icon = icon("dashboard")),
menuItem("Results", tabName = "tabItem2", icon = icon("th"))
)
),
dashboardBody(
tags$script(HTML("
var openTab = function(tabName){
$('a', $('.sidebar')).each(function() {
if(this.getAttribute('data-value') == tabName) {
this.click()
};
});
}
")),
tabItems(
tabItem(tabName = "tabItem1",
fluidRow(
box(plotOutput("plot1", height = 250)),
box(
title = "Controls",
sliderInput("slider", "Number of observations:", 1, 100, 50)
)
),
infoBoxOutput("out1")
),
tabItem(tabName = "tabItem2",
h2("Widgets tab content")
)
)
)
)
)
server <- function(input, output){
histdata <- rnorm(500)
output$plot1 <- renderPlot({
data <- histdata[seq_len(input$slider)]
hist(data)
})
output$out1 <- renderInfoBox({
infoBox("Completed",
a("Computation Completed", onclick = "openTab('tabItem2')", href="#"),
icon = icon("thumbs-o-up"), color = "green"
)
})
}
shinyApp(ui, server)
Note, that the only important thing is the onclick property, not an href. This means, that every div or other element can be used to create this link. You could even have just the thumbs-up image with this onclick command.
If you have more questions, please comment.
Best Regards
Edit: Whole infoBox clickable.
This is an answer to a comment by OmaymaS. The point was to make the infoBox a clickable container. To achieve this, one can define a new function that makes a somewhat different infoBox. The custom box will be as follows:
customInfoBox <- function (title, tab = NULL, value = NULL, subtitle = NULL, icon = shiny::icon("bar-chart"), color = "aqua", width = 4, href = NULL, fill = FALSE) {
validateColor(color)
tagAssert(icon, type = "i")
colorClass <- paste0("bg-", color)
boxContent <- div(class = "info-box", class = if (fill) colorClass,
onclick = if(!is.null(tab)) paste0("$('.sidebar a')).filter(function() { return ($(this).attr('data-value') == ", tab, ")}).click()"),
span(class = "info-box-icon", class = if (!fill) colorClass, icon),
div(class = "info-box-content",
span(class = "info-box-text", title),
if (!is.null(value)) span(class = "info-box-number", value),
if (!is.null(subtitle)) p(subtitle)
)
)
if (!is.null(href)) boxContent <- a(href = href, boxContent)
div(class = if (!is.null(width)) paste0("col-sm-", width), boxContent)
}
This code is copied from the original infoBox function definition and only the line with onclick is new. I also added the openTab function (with some twitches) right inside the container such that you dont need to worry where to put this function inside the view. Might be a bit overloaded i feel.
This custom info box can be used exactly like the default one and if you pass the additional tab argument, the link to the sidebar is added.
Edit: Subtitle exploit
As Alex Dometrius mentioned, the use of subtitle crashes this functionality. This is because the script tag that was inserted, on accident, was used as the subtitle argument in order to be rendered with the box. To free up this spot, I edited the main example up top such that the script tag is sitting top level in the dashboardBody (literally anywhere in the ui would be fine).
(To avoid confusion: in Version 1, the tags$script was supplied inside of infobox where it was interpreted as the subtitle parameter.)
Related
I got an app that opens up a modalDialog with an image inside a spsComps::gallery. However, the enlargement works only the first time the modal has been opened. How can this be fixed? Here is a minimal reprex:
library(shiny)
library(spsComps)
ui <- fluidPage(
actionButton("modal", "Open modal")
)
server <- function(input, output, session) {
observeEvent(input$modal,
{
showModal(modalDialog(
title = "test",
fluidRow(gallery(
texts = "Click to enlarge", hrefs = "", image_frame_size = 6,
images = "https://cdn.pixabay.com/photo/2018/07/31/22/08/lion-3576045__340.jpg",
enlarge = TRUE, title = "When you close this modal, the enlargement does not work again",
enlarge_method = "modal"
)),
footer = modalButton("Cerrar"),
easyClose = TRUE,
size = "xl"))
})
}
shinyApp(ui, server)
The first time the modal is opened, you can enlarge the image by clicking it. It looks like this:
However, when you close and then reopen the modal, that enlargement feature is missing.
A temporary fix would be this:
library(shiny)
library(spsComps)
ui <- fluidPage(
actionButton("modal", "Open modal"),
singleton(
div(id = "sps-gallery-modal", class = "gallery-modal",
style="display: none;",
onclick = "galModalClose()", tags$span(class = "gallery-modal-close", "X"),
tags$img(id = "sps-gallery-modal-content",
class = "gallery-modal-content"),
div(class = "gallery-caption")
))
)
server <- function(input, output, session) {
observeEvent(input$modal,
{
showModal(modalDialog(
title = "test",
fluidRow(gallery(
texts = "", hrefs = "", image_frame_size = 6,
images = "https://cdn.pixabay.com/photo/2018/07/31/22/08/lion-3576045__340.jpg",
enlarge = TRUE, title = "When you close this modal, the enlargement does not work again",
enlarge_method = "modal"
)),
footer = modalButton("Cerrar"),
easyClose = TRUE,
size = "xl"))
})
}
shinyApp(ui, server)
The reason is I have this singleton(... in the gallery creation function. There is only one enlarge img container needed to be created no matter how many galleries you have (It's not practical to enlarge two pictures at the same time). So enlarged images from different galleries are displayed inside the same enlarge container. This saves computer resources, and singleton in Shiny is the function to prevent duplication. Even if you may call gallery many times, if the content inside singleton is sent to the DOM tree only once, it will not append it again.
The problem is when showModal is closed, Shiny deletes everything inside the modal, including the gallery singleton content. Meanwhile, I think the singleton content validation stays at R level. It does not actually go search the DOM tree if this content exists or not. So Shiny thinks singleton content is there, and therefore refused to send it to DOM when the second time you call showModal.
The fix above append singleton content to fluidPage container instead of Shiny modal container, so when modal is closed, it cannot delete the content.
This is a universal problem in Shiny when you have singleton and modalDialog. There is nothing I can do to fix Shiny, but I may think of a more user-friendly way in the next spsComps version to address it.
In the Shiny App below, I am facing a very strange behavior, where selectInput box slides downwards when I type something in this box. Also, the text inside selectInput box moves towards the right while I type in this box. I have spent a lot of time to find out the reason for this problem but could not figure it out. Can someone point out the mistake I am doing causing this strange behavior?
library(shiny)
library(shinydashboard)
library(highcharter)
siderbar <- dashboardSidebar(
sidebarMenu(
selectizeInput(inputId = "select_by", label = "Select by:", choices = NULL, multiple = FALSE, options = NULL)
)
)
body <- dashboardBody(
fluidRow(
tabBox(
side = "right",
selected = "Tab1",
tabPanel("Tab1", "Tab content 1", highchartOutput("tabset1Selected"))
)
),
)
shinyApp(
ui = dashboardPage(
dashboardHeader(title = "tabBoxes"),
siderbar,
body
),
server = function(input, output, session) {
selectedVal <- reactiveValues()
updateSelectizeInput(session, "select_by", choices = c(as.character(1:10000)), selected = 2, server = TRUE)
output$tabset1Selected <- renderHighchart({
selectedVal <- input$select_by
print(highcharts_demo())
})
}
)
We were on the right track. It has something to do with selectize.js updating the items from the server. You can verify that by setting the loadThrottle option to 5000. This option determines how long the widget waits "before requesting options from the server" (see the manual). Now you have to wait exactly 5 seconds and then the select widget flickers.
The issue seems to be caused by a CSS conflict. selectize.js adds a CSS class to the widget. If you remove that feature, the flicker goes away.
selectizeInput(inputId = "select_by", label = "Select by:",
choices = NULL, multiple = FALSE,
options = list(loadThrottle=200, loadingClass=""))
loadingClass sets a specific CSS class (default: 'loading') while loading data from the server. Purpose: to change how the widget looks and communicate to users that an update is in progress.
loadThrottle does not need to be set. It's default is 300. You can set it to any value that suits your needs.
Details
highcharter defines it's own CSS class names loading with these specs:
.loading {
margin-top: 10em;
text-align: center;
color: gray;
}
That is the reason for the CSS conflict. The widget gets a top margin and it's content moved to the center, because the browser does not distinguish the source of the class. It only sees some CSS that fits and uses it. This image shows where you need to look:
I have a button in my ui.R that I want to be shown only when "Summary" tab is selected, so I thought of this code
fluidRow(
column(4,
column(12,id="sub",
actionButton("submit", "SUBMIT", width = "100%"))),
column(8,
bsCollapse(id = "collapse7", open = "Results",
bsCollapsePanel("Results",
tabsetPanel(
tabPanel("Summary",
tags$script(HTML("document.getElementById('sub').style.visibility = 'visible';")))
tabPanel("Plot",
tags$script(HTML("document.getElementById('sub').style.visibility = 'hidden';"))))
))))
The problem is, the button is hidden even though in my first tab it should be visible and also when i go to Plots and back to Summary, the button stays hidden.
After looking at: How to use tabPanel as input in R Shiny?
I decided to play with observeEvent and the input$tabset option. The result is 100% working and it's really simple. Here's the code:
observeEvent(input$choices, {
choice = input$choices
if(choice == "Summary")
{
runjs(
"document.getElementById('submit').style.visibility = 'visible';"
)
}
else
{
runjs(
"document.getElementById('submit').style.visibility = 'hidden';"
)
}
})
Also, I found out why my previous code wasn't working, it was due to the fact that when the UI was initialized, the button element kept the last style modification (the hidden one) and it didn't change depending on the tab I have selected, since its not reactive.
I have a problem with shiny tabs. I want to create a navigation page with two tabs. Right to them, I would like to insert some user's login details. There is no option "text" or other to insert a text in the navbarPage. But I created an additionnal tab instead:
library(shiny)
runApp(list(
ui = navbarPage(
title="My App",
tabPanel("tab1 title"),
tabPanel("tab2 title"),
tabPanel("User: Madzia")),
server = function(input, output) { }
))
It is OK like this, but I do not want the third tab to be "selectible": I want it to be disabled, so that we cannot click on it - the same as on "My App" text. Do you have any idea about how to handle this problem?
Thank you! Best, Madzia
You can achieve disabling a tab with a tiny bit of javascript. I have an example of how to hide a tab (not disable) in recent blog post, you can see the code for that here. I modified that code a bit for disabling instead.
This code is hacky because it was done in 2 minutes but will work for a basic use case
library(shiny)
library(shinyjs)
jscode <- '
shinyjs.init = function() {
$(".nav").on("click", ".disabled", function (e) {
e.preventDefault();
return false;
});
}
'
css <- '
.disabled {
background: #eee !important;
cursor: default !important;
color: black !important;
}
'
shinyApp(
ui = fluidPage(
useShinyjs(),
extendShinyjs(text = jscode, functions = "init"),
tags$style(css),
checkboxInput("foo", "Disable tab2", FALSE),
tabsetPanel(
id = "navbar",
tabPanel(title = "tab1",
value = "tab1",
h1("Tab 1")
),
tabPanel(title = "tab2",
value = "tab2",
h1("Tab 2")
),
tabPanel(title = "tab3",
value = "tab3",
h1("Tab 3")
)
)
),
server = function(input, output) {
observe({
toggleClass(condition = input$foo,
class = "disabled",
selector = "#navbar li a[data-value=tab2]")
})
}
)
Edit I didn't fully read the question when I posted my answer, I just saw that you wanted a way to disable a tab and that was my answer. Your specific usecase (creating a tab only to show the name of a user) is a bit strange, but I suppose this will still work...
I would like to keep my previous answer in existence because it may be useful for someone in the future who wants to know how to disable a tab.
But for this specific problem, disabling the tab is not the correct approach. It makes more sense to simply add text to the tab (as Valter pointed out in a comment). If you look at the documentation for bootstrap, it says you can add text into the navbar by adding an html element with class navbar-text. I experimented with the HTML a little bit to figure out exactly where this needs to be done, and created a little function that will wrap around navbarPage() to allow you to add text to it.
Here's an example:
library(shiny)
navbarPageWithText <- function(..., text) {
navbar <- navbarPage(...)
textEl <- tags$p(class = "navbar-text", text)
navbar[[3]][[1]]$children[[1]] <- htmltools::tagAppendChild(
navbar[[3]][[1]]$children[[1]], textEl)
navbar
}
ui <- navbarPageWithText(
"Test app",
tabPanel("tab1", "tab 1"),
tabPanel("tab2", "tab 2"),
text = "User: Dean"
)
server <- function(input, output, session) {
}
shinyApp(ui = ui, server = server)
In RShiny is there any way to send an array or list of menuItems to sidebarMenu?
dashboardSidebar(width = 180,
sidebarMenu(
menuItem("server1", tabName = "server1", icon = icon("server")),
menuItem("server2", tabName = "server2", icon = icon("server")),
menuItem("server3", tabName = "server3", icon = icon("server"))
)
)
I actually have to add about 30 or menuItems to add and the names will change over time. This is also an issue with sending multiple fluidRows to a tabItem. Hopefully, the same solution can apply to both.
There is the possibility to use uiOutput, but personally, I am not a fan. However, there is not a single solution for all dynamic element additions, especially when not using uiOutput.
Here is a minimal solution that does what you said you wanted, but I guess this is only the first step for you (since I don't think you really just want to add menuItems). If you are looking for a more customized answer, please edit your original post with your actual problems.
You can add menuItems to the sidebar very easily, if you use JavaScript/jQuery. This is mainly because menuItems are not really "connected" to the Shiny-App but merely toggle buttons. It gets more difficult if you wanted to add complex reactive elements.
There is basically the trick that you give the target element (sidebar) an Id for easy selection and add the HTML that is generated by the shiny ui functions. The customMessageHandler gets both components and tells the browser to create the element.
More on customMessageHandlers can be found here.
Sample code below:
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(width = 180,
sidebarMenu(id = "nice",
menuItem("server1", tabName = "server1", icon = icon("server")),
menuItem("server2", tabName = "server2", icon = icon("server")),
menuItem("server3", tabName = "server3", icon = icon("server"))
)
),
dashboardBody(
actionButton("button", "Add MenuItem"),
tags$script("
Shiny.addCustomMessageHandler('addMenuItem', function(message){
$('#' + message[0]).append(message[1]);
})
")
)
)
server <- function(input, output, session){
addMenuItem <- function(item, sidebarId){
session$sendCustomMessage(type = "addMenuItem", message = list(sidebarId, as.character(item)))
}
observeEvent(input$button, {
newId <- as.numeric(input$button) + 3
newMenuItem <- menuItem(paste0("server", newId), tabName = paste0("server", newId), icon = icon("server"))
addMenuItem(newMenuItem, "nice")
})
}
shinyApp(ui, server)