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.
Related
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 am using a popup window in R Shiny with the following code:
library(shiny)
library(shinyjqui)
ui = basicPage(
actionButton("show", "Show modal dialog"),
textAreaInput(
inputId = 'textEditor',
label = NULL,
value = "R is a free software environment for statistical computing and graphics.",
height = "300",
resize = "none"
)
)
server = function(input, output)
{
observeEvent(input$show,
{
showModal(draggableModalDialog(
title = "Add the following text in the box at the left:",
"The R language is widely used among statisticians and data miners.",
footer = tagList(
actionButton("ok", "OK")
)
))
})
observeEvent(input$ok,
{
removeModal()
print("OK")
})
}
shinyApp(ui = ui, server = server)
It strikes me that when the popup window is open, you can not use the elements on the background. The whole background is greyed-out.
In most cases this may be the right behaviour, but in my case I would like to be able to edit the text in the left window while the popup window is open.
Is it possible to make this possible? If so, how?
You are trying to use a modal dialog in a way it is not intended to be used, so you need to make some manual changes in its behaviour. There are three problems you need to solve to fully remove the gray background and allow interactions with everything in the background:
You have to hide the backdrop (the gray background) itself.
The movable modal has a parent overlay that covers the full screen in order to allow free movement. This overlay captures all pointer events and makes everything below it unclickable.
The draggableModalDialog element has attribute tabindex="-1", which is a HTML trick that prevents interactions with input fields outside of the modal. See the source on Github.
Problems #1 and #2 are solvable with a little CSS:
ui = basicPage(
tags$head(tags$style(HTML("
.modal-backdrop { # hide backdrop
display: none;
}
.modal { # pass through clicks etc. on the overlay
pointer-events: none;
}
.modal-dialog { # do capture mouse events on the modal itself
pointer-events: all;
}"
))),
[...]
)
For problem #3 you actually need to modify the draggableModalDialog function. You can copy-paste the original definition and remove the tabindex definition:
customDraggableModalDialog <- function(..., title = NULL,
footer = shiny::modalButton("Dismiss"),
size = c("m", "s", "l"),
easyClose = FALSE, fade = TRUE) {
size <- match.arg(size)
cls <- if (fade) { "modal fade" } else { "modal" }
shiny::div(
id = "shiny-modal",
class = cls,
# tabindex = "-1", This line should be commented out or removed
`data-backdrop` = if (!easyClose) { "static" } ,
`data-keyboard` = if (!easyClose) { "false" } ,
shiny::div(
class = "modal-dialog",
class = switch(size, s = "modal-sm", m = NULL, l = "modal-lg"),
jqui_draggable(shiny::div(
class = "modal-content",
if (!is.null(title)) {
shiny::div(
class = "modal-header",
shiny::tags$h4(class = "modal-title", title)
)
},
shiny::div(class = "modal-body", ...),
if (!is.null(footer)) {
shiny::div(class = "modal-footer", footer)
}
))
),
shiny::tags$script("$('#shiny-modal').modal().focus();")
)
}
Then you can replace uses of draggableModalDialog with customDraggableModalDialog.
Resolution:
We had been struggling with modals opening in background since 6 months and the following settings has resolved it for all our clients:
Change the cache behavior in IE from “automatic” to “Every time the page changes” and you will never face this quirky issue :)
I want to have scroll bar to scroll up and down, cross button to close the pop up window and default of 10 records should display instead of 25 now.
I don't know how to write code for this.
library(shiny)
library(shinydashboard)
library(shinyjs)
library(shinyBS)
data <- iris
ui <- tagList(
useShinyjs(),
dashboardPage(
dashboardHeader(title = "Telemedicine HP"),
dashboardSidebar(),
dashboardBody(
fluidRow(
div(id='clickdiv',
valueBox(60, subtitle = tags$p("Attended", style = "font-
size: 200%;"), icon = icon("trademark"), color = "purple", width = 4,
href
= NULL)
)
)
)
)
)
server <- function(input, output, session){
onclick('clickdiv', showModal(modalDialog(
title = "Your title",
renderDataTable(data)
)))
}
shinyApp(ui, server)
By clicking on valuebox a pop up window will appear showing some tabular data.
But that window should have a scroll bar, cross button in right top corner and records should be shown 10 by default instead of 25 showing now in top left corner of the pop up window.
Can anyone help me with this ?
If your server part is like this, it is limited to 10 shown per page:
server <- function(input, output, session){
onclick('clickdiv', showModal(modalDialog(
title = "Your title",
renderDataTable(data, options = list(
pageLength = 10,
scrollY = "400px"
))
)))
}
I'm not sure I understand the need for the other parts. With 10 records, you don't need to be able to scroll up and down, but even when I set this to a lot of records (say 100), the normal page scroll bar works fine. And there is a button to dismiss the table already (although I appreciate it is not the cross in the corner you are requesting).
You can change other parts of your DataTable using the options - you can see some examples here.
Hope this helps!
EDIT: I've added an option for a vertical scroll bar. You can change the number to suit you.
If that doesn't work then you might be using a setup (for eg Mac) where scrollbars are hidden by default until you start scrolling.
I am working on a shiny app, that reads data from a file, and display the data on the app, and also allows user to refresh the data. The app works fine, except that when I 'refresh' the data with the action button, some styling are gone.
Below is a simplified version of my app.R
library(shiny)
file_name <- "sample.csv"
bkg_color <- "red"
# Define UI for application
ui <- fluidPage(
actionButton("refresh", "", icon("refresh") ),
tableOutput("table"),
uiOutput("slider")
)
# Define server logic required
server <- function(input, output, session) {
observeEvent(input$refresh,{
source("updatedata.R")
showModal(modalDialog(
title = "",
"Data refreshed",
easyClose = TRUE,
footer = NULL
))
})
# observe the raw file, and refresh if there is change every 5 seconds
raw <- reactivePoll(5000, session,
checkFunc = function(){
if (file.exists(file_name))
file.info(file_name)$mtime[1]
else
""
},
valueFunc = function(){
read.csv(file_name)
})
output$table <- renderTable(raw())
output$slider <- renderUI({
req(raw())
tagList(
# styling slider bar
tags$style(HTML(paste0(".js-irs-0 .irs-single, .js-irs-0 .irs-bar-edge, .js-irs-0 .irs-bar {background: ",
bkg_color,";border-top: ",bkg_color,";border-bottom: ",bkg_color,"; border: ",bkg_color,"}"))),
sliderInput("date","",
min = min(raw()$v1),
max = max(raw()$v1),
value = max(raw()$v1))
)
})
}
# Run the application
shinyApp(ui = ui, server = server)
In the above, I used renderUI for my slider, as the values depends on the raw values I read from the local file. And I specify the color for the slider explicitly (currently set to red).
And in the same directory, I have updatedata.R that does something similar to the below:
file_name <- "sample.csv"
temp <- data.frame(v1 =runif(10, min = 0, max = 100), v2 = Sys.time() )
write.csv(x =temp, file = file_name,row.names = FALSE )
To run the sample app without error, please run the above code first to initialize the csv files.
When the app first launches, the slider bar is red color. However, after I refresh the underlying data by clicking on the refresh button at the top of the app [NOT the browser refresh], the slider bar changed back to the default shiny app color.
I've searched for an answer for this for quite some time, but cannot even figure out what is the root cause for this. Does anyone has experienced similar issue before, or have an idea how I can fix it, so that the color of the slider bar is unchanged after the refresh?
Thank you!
Shiny increments the slider class each time a new slider is rendered.
therefore the initial class becomes .js-irs-1 on refresh, then .js-irs-2 etc.
change your css selector to .irs child as follows:
tags$style(HTML(paste0(".irs .irs-single, .irs .irs-bar-edge, .irs .irs-bar {background: ",
bkg_color,";border-top: ",bkg_color,";border-bottom: ",bkg_color,"; border: ",bkg_color,"}")))
however i would recommend using server side logic to update the input. It's usually better practice since the html element is rendered on website and only certain values are updated not the whole element.
check updateSliderInput() function to update your slider
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.)