I am trying to update divs while in a loop, some of which contain images. Using removeUI(..., immediate = TRUE) I can remove them and then replace them by new divs, with insertUI(..., immediate = TRUE). Although the texts appear in real time, the images do not load until we are out of the loop (see example below, you don't even have to load an image, a question mark will appear after the loop ends).
In my browser I can see the img tags are created in HTML, but still no images appear live.
Here is a reproducible example:
ui <- fluidPage(
actionButton("add","")
)
server <- function(input, output, session) {
for(i in 1:3){
Sys.sleep(1.5)
insertUI(
selector = "#add",
where = "afterEnd",
ui = div(style = paste0("width: 75px; height: 75px; background-color: white;"), h5("Text appears live", align = "center"),
div(h6("Text inside a div appears live")),
div(id = "img", img(src = "image.jpg", alt = "Images do not appear live")
)
),
immediate = TRUE
)
}
}
shinyApp(ui, server)
Is this normal behavior for shiny? If so is their a way to bypass it and to see the images appear directly? Another way to do it?
Here's a slightly more self-contained set of code that works for me if I run the app by hitting the "Run App" button in Rstudio.
dir.create("www")
dir.create("www/images")
library(shiny)
library(magick)
green.square <- image_blank(width=50, height =75, color= "green")
grid.total.squares <- 12*8
wordList <- 1:(grid.total.squares*2)
for (i in seq_along(wordList)){
thisImage = image_annotate(green.square, gravity="center", i,
size=30)
image_write(thisImage, format = "png", path = paste0("www/images/",i, ".png"))
}
ui <- fluidPage(
actionButton("add","Add something"),
)
server <- function(input, output, session) {
for(i in 1:3){
Sys.sleep(.5)
insertUI(
selector = "#add",
where = "afterEnd",
ui = div(style = paste0("min-width:75px; min-height: 75px; background-color: white; clear:both;"), h5("Text appears live", align = "center"),
div(h6("Text inside a div appears live")),
div(id = "img",
img(src = paste0("images/",i,".png"),
alt = "Images do not appear live"
),
hr()
)
),
immediate = TRUE
)
}
}
shinyApp(ui, server)
Related
I am building a shiny dashboard and plan to use SliderInput to animate a set of exisiting pngs. To do so, in the UI I have:
tabItem(tabName = 'Image',
fluidRow(
box(title = "", status="primary",solidHeader = F,width = 9,
uiOutput("animate_img"),
tags$style(type="text/css", "recalculating { opacity: 1.0 !important; }") # NOT WORKING
),
box(
title = "Options", status="info",solidHeader = TRUE,width = 3,
sliderInput("dates_img",
"Dates:",
min = as.Date("2017-01-01","%Y-%m-%d"),
max = as.Date("2018-12-31","%Y-%m-%d"),
value=as.Date("2017-01-01"),
timeFormat="%Y-%m-%d",
animate=animationOptions(interval=1000, loop = TRUE))
)
)
)
and in the server I have:
output$animate_img <- renderUI({
y <- year(input$dates_img)
d <- yday(input$dates_img)
filename <- sprintf("img_%d_%d.png",d,y)
tags$img(src = filename, width="100%")
})
While this code works to display the images, when I use the "play" button on the sliderInput to animate the images, there is flickering as each image loads. I would like to have a smooth animation if possible.
As suggested here, I have tried adding tags$style(type="text/css", "recalculating { opacity: 1.0 !important; }") to the UI, but this does not work.
Any recommendations for how to prevent the images from flickering as the animation plays? Thank you!
I was able to get it to work without any flickering by simply adjusting how the CSS is included in the rendered HTML. I used shinyjs::inlineCSS in my example, but the same could be done via sourcing an external stylesheet .css file with tags$head and tags$script or via includeCSS, etc. The key is to have the CSS loaded into the full HTML document's head (can verify via browser DevTools):
library(shiny)
library(shinydashboard)
library(shinyjs)
library(lubridate)
ui <- fluidPage(
shinyjs::inlineCSS(
"recalculating { opacity: 1.0 !important; }"
),
fluidRow(
box(title = "",
status = "primary",
solidHeader = F,
width = 9,
uiOutput("animate_img")
),
box(
title = "Options",
status = "info",
solidHeader = TRUE,
width = 3,
sliderInput("dates_img",
"Dates:",
min = as.Date("2017-01-01","%Y-%m-%d"),
max = as.Date("2018-12-31","%Y-%m-%d"),
value = as.Date("2017-01-01"),
timeFormat = "%Y-%m-%d",
animate = animationOptions(interval = 1000, loop = TRUE))
)
)
)
server <- function(input, output) {
output$animate_img <- renderUI({
y <- year(input$dates_img)
d <- yday(input$dates_img)
filename <- sprintf("img_%d_%d.png",d,y)
tags$img(src = filename, width="100%")
})
}
shinyApp(ui = ui, server = server)
Just make sure that your image files are placed directly in the www folder and it should work.
Thanks,
Jimmy
I'm developing an R Shiny app and am trying to append two output objects side-by-side as part of the same UI element. However, when I use splitLayout() Shiny creates a space between the two objects highlighted below:
Is there a way to get the two objects to appear immediately side-by-side without the space in between? Please see code behind stylized example below:
# define mapping table
col1 <- c("AAAA" , "BBBB" , "CCCC" , "DDDD")
col2 <- c(1:4)
map <- as.data.frame(cbind(col1, col2))
# define and execute app
ui <- fluidPage(
selectInput(inputId = "object_A", label = "Select Object A",
choices = c("AAAA", "BBBB" , "CCCC"), selected = NULL, multiple = FALSE),
actionButton("go","Run Output"),
tags$br(),
fluidRow(
column(width = 4,
uiOutput(outputId = "select_object")
)
)
)
server <- function(input, output) {
observeEvent(input$go, output$select_object <-
renderUI({
splitLayout(
input$object_A,
map[which(map["col1"]==input$object_A),"col2"]
)
})
)
}
shinyApp(ui = ui, server = server)
You can use a flexbox:
observeEvent(input$go, {
output$select_object <-
renderUI({
div(
style = "display:-webkit-flex; display:-ms-flexbox; display:flex;",
div(input$object_A),
div(style = "width: 30px;"), # white space
div(map[which(map["col1"]==input$object_A),"col2"])
)
})
})
To center the flexbox items:
style = "display:-webkit-flex; display:-ms-flexbox; display:flex; justify-content:center;"
More info on flexbox: guide to flexbox.
For text only, you could use paste instead of splitLayout :
observeEvent(input$go, output$select_object <-
renderUI({
paste(
input$object_A,
map[which(map["col1"]==input$object_A),"col2"]
)
})
)
I have a shiny app with list of wellPanels. They are used in jqui_sortable from shinyjqui. Panels are generated in server part (to uiOutput in ui). Order of panels can be changed by mouse and is written to file (by ids). Then I would like to open this file and change default order with loaded data.
Issue: I can't get out of rendered words "div" between panels (run code below).
Code was written with some lines from solution (thanks to #TimTeaFan):
Distorted spacing between div elements after sorting with jqui_sortable
library(shiny)
library(shinyjqui)
ui <- fluidPage(
sidebarLayout(fluid = TRUE,
sidebarPanel(helpText("HelpText")),
mainPanel(
fluidRow(column(12,
actionButton(inputId = "btn1",label = "Button1"),
tags$style(HTML(".ui-sortable {
width: 1200px !important;
} ")),
uiOutput('multiobject'),
actionButton(inputId = "btn2",label = "Button2")
))
)
)
)
server <- function(input, output, session) {
sortableorderednameList<-reactiveVal(
c("A","B","C")
)
wpFunc <- function(v,name,helptext){
return(tags$div(wellPanel(id=paste0(v,"P"),
div(style="display: inline-block; width: 10px;",
checkboxInput(paste0(v,"Chk"), label = NULL, value = TRUE)),
div(style="display: inline-block; width: 150px;",
textInput(paste0(v,"TI"), label = NULL, value = name)),
div(style="display: inline-block;",helpText(helptext)),
style = "padding: 1px;")))
}
observe({
if(is.null(input$sortablecollistJQ_order$id)) {return()}
mylist <- input$sortablecollistJQ_order$id
mylist <- unlist(lapply(mylist, function(v) substr(v,1,nchar(v)-1)))
print(mylist)
print(" ")
isolate(sortableorderednameList(mylist))
})
output$multiobject <- renderUI({
uiList <- list()
for (v in sortableorderednameList()) {
switch(v,
"A" = {uiList <- append(uiList,wpFunc(v,"A","There is A"))},
"B" = {uiList <- append(uiList,wpFunc(v,"B","There is B"))},
"C" = {uiList <- append(uiList,wpFunc(v,"C","There is C"))}
)
}
jqui_sortable(div(id = 'sortablecollistJQ',uiList))
})
}
shinyApp(ui, server)
I have got an answer after experiments. If somebody is interested.
for (i in 1:length(uiList)) {
uiList[i] <- uiList[i]$children
}
It changes structure of list, put it before jqui_sortable call.
Hello Stack Overflow,
in recent questions by me I've solved some major issues related to dynamically rendered UI elements and dynamically created observers for those with the help of some amazing people here. see i.e. here:
Dynamically rendered UI: how to delete old reactive variables on second run
Now I'm building a part of it that dynamically renders textInput fields. The rendering and monitoring shouldn't be a problem because I can apply the same way of coding as for the actionbuttons we've already made, but the styling of these elements is proving a problem.
As far as I know there are 2 ways to style elements:
add tags$style(.....) to them like,
1:
tags$style(type="text/css", "#BatchName { width: 520px; position: relative;left: 7%}")
in the UI
or 2:
actionButton(inputId= "Submit", label = icon("upload"),
style="color: blue; color: white;
text-align:center; indent: -2px;
border-radius: 6px; width: 2px"),
The 2nd option also works for the dynamic rendering as seen in the link above, and would work in the example below as well if I was to make actionButtons instead of textInput in the lapply loop in the working example below. However, the style = "......" element inside a textInput() doesn't work.
Does anyone have a solution to also add style dynamically to textinput?
Solutions I've tried but failed:
dynamically making tags$head elements but it's not a ui element as could be made with renderUI() I think
Somehow making textinput accept the style = " ") argument.
Finally I had a look at the function code of textInput and wondered if plan A or B don't work, whether it would be possible to modify the existing textInput code into my own function with more freedom?
textinput is coded like this in the package:
function (inputId, label, value = "", width = NULL, placeholder = NULL)
{
value <- restoreInput(id = inputId, default = value)
div(class = "form-group shiny-input-container", style = if (!is.null(width))
paste0("width: ", validateCssUnit(width), ";"), label %AND%
tags$label(label, `for` = inputId), tags$input(id = inputId,
type = "text", class = "form-control", value = value,
placeholder = placeholder))
}
WORKING EXAMPLE:
library(shiny)
library(shinydashboard)
library(shinyBS)
ui <- dashboardPage(
dashboardHeader(title = "My Test App"),
dashboardSidebar(
sidebarMenu(id = "tabs", menuItem("testpage", tabName = "testpage", icon = icon("book"))
)
),
dashboardBody(
tags$head(tags$style(HTML('.skin-blue .content-wrapper, .right-side {background-color: #ffffff; }, '))),
tabItems(
### test page ###_________
tabItem(tabName = "testpage",
h5("Enter desired nr of elements here"),
textInput(inputId ="NrOfClusters", label = NULL , placeholder = "NULL"),
uiOutput("NameFields")
))))
shinyServer<- function(input, output, session) {
################# start functionality HOME TAB #############################
### create 2 reactive environment lists
values <- reactiveValues(clickcount=0)
DNL <- reactiveValues(el=NULL)
### set initial state of two buttons
values$HL_multi_switch_sf1 <- FALSE
values$HL_all_switch_sf1 <- FALSE
### if the user types in a value, then convert it to a reactive value of this nr
observeEvent (input$NrOfClusters, {
values$nrofelements <- input$NrOfClusters
namelist <- as.character(unlist(DNL$el), use.names = FALSE)
})
AddNameField <- function(idx){
sprintf("highlight_button_sf1-%s-%d",values$nrofelements,idx)
}
#### RENDER DYNAMIC UI and DYNAMIC OBSERVERS
observeEvent(values$nrofelements, {
req(input$NrOfClusters)
nel <- values$nrofelements
DNL$el <- rep(0,nel)
names(DNL$el) <- sapply(1:nel,AddNameField)
output$NameFields <- renderUI({
lapply(1:values$nrofelements, function(ab) {
div(br(), textInput(inputId = AddNameField(ab), label = NULL))
})
})
lapply(1:values$nrofelements, function(ob) {
textfieldname <- AddNameField(ob)
print(textfieldname)
observeEvent(input[[textfieldname]], {
DNL$el[[ob]] <- input[[textfieldname]]
namelist <- as.character(unlist(DNL$el), use.names = FALSE)
print(namelist)
})
})
})
}
options(shiny.reactlog = TRUE)
shinyApp(ui,shinyServer)
I use shinyBS::bsModal() to place explanations of the UI elements there. It works great when I place a bsButton() behind the title of the checkbox.
Now I want to place it behind the checkbox options.
A first hint could be this answer where the same for a tooltip is done (but my modification do not work).
Minimal Example:
library(shiny)
library(shinyBS)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
checkboxGroupInput("qualdim",
tags$span("Chekboxoptions",
bsButton("modalbt", "?", style = "inverse", size = "extra-small")),
c("Option_1" = "Option_1",
"Option_2" = "Option_2"))
),
mainPanel(
bsModal("modalExample", "Modal", "modalbt", size = "large",
verbatimTextOutput("helptext")))
)
)
server <- function(input, output) {
output$helptext <- renderText({"I can trigger a shinyBS::bsModal() from here, but I want to place two buttons behind `Option_1` and `Option_2`" })
}
shinyApp(ui = ui, server = server)
The bsModal works anywhere and just takes the button id as a trigger. So the only thing you need to do is to get a suitable button inside the checkboxGroup. From the previous Question/Answer you linked, you already have the function to get a bsButton inside the group input. (Just erase the line where the tooltip has been assigned. This is not needed here.)
The code below basically is copy paste now. I just added some extra bsButton settings like size, style and id (this one is important! was not important in the linked question with the tooltips!), such that you can use the function more like you would use bsButton.
library(shiny)
library(shinyBS)
makeCheckboxButton <- function(checkboxValue, buttonId, buttonLabel, size = "default", style = "default"){
size <- switch(size, `extra-small` = "btn-xs", small = "btn-sm",
large = "btn-lg", "default")
style <- paste0("btn-", style)
tags$script(HTML(paste0("
$(document).ready(function() {
var inputElements = document.getElementsByTagName('input');
for(var i = 0; i < inputElements.length; i++){
var input = inputElements[i];
if(input.getAttribute('value') == '", checkboxValue, "'){
var button = document.createElement('button');
button.setAttribute('id', '", buttonId, "');
button.setAttribute('type', 'button');
button.setAttribute('class', '", paste("btn action-button", style , size), "');
button.appendChild(document.createTextNode('", buttonLabel, "'));
input.parentElement.parentElement.appendChild(button);
};
}
});
")))
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
checkboxGroupInput("qualdim", label = "Chekboxoptions", choices = c("Option_1", "Option_2")),
makeCheckboxButton("Option_1", "modalbt", "?", size = "extra-small", style = "inverse")
),
mainPanel(
bsModal("modalExample", "Modal", "modalbt", size = "large",
verbatimTextOutput("helptext")))
)
)
server <- function(input, output) {
output$helptext <- renderText({"I can trigger a shinyBS::bsModal() from here, but I want to place two buttons behind `Option_1` and `Option_2`" })
}
shinyApp(ui = ui, server = server)