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)
Related
I have two tables and I'm trying to show one at a time based on user input in radio buttons. If the input from the radio buttons is "table", i'd like to show table1. If the input is else i'd like to show table2.
observeEvent(input$visuBtn,{
req(input$visuBtn)
print(input$visubtn)
if(input$visuBtn == "table"){
hide("table2")
#DT::dataTableOutput("table1")
renderUI(
DT::dataTableOutput("table1")
)
}else{
print("Should show table2")
# removeUI(
# selector = "table"
# )
renderUI(
DT::dataTableOutput("table2")
)
#DT::dataTableOutput("table2")
#show("table2")
}
})
I've tried doing this by showing and hiding the two tables and can't figure out how to get that to work. I"ve also tried using renderUI as well. What would be the best methodology to go about this?
mainPanel(
tabsetPanel(id = "sim.tabset",
tabPanel(title = "Results",
# tableOutput("table")
DT::dataTableOutput("table"),
DT::dataTableOutput("table2")
),
)
Depending on your app, you can toggle the visibility of the table in the frontend with a little bit of javascript. In the UI, create a button and wrap the dataTableOutput in a generic container.
# some where in your UI
actionButton("toggleTable", "Toggle Table"),
tags$div(
id = "tableContainer",
DT::dataTableOutput("table")
)
...
There are many ways to toggle the visibility of an element (changing the display properties, toggling css classes, modifying other attributes, etc.). The following function toggles the html attribute hidden when the button is clicked. This can be defined in the UI using the tags$script function or loaded from an external javascript file.
const btn = document.getElementById('toggle');
const elem = document.getElementById('tableContainer');
btn.addEventListener('click', function(event) {
if (elem.hasAttribute('hidden')) {
elem.removeAttribute('hidden');
} else {
elem.setAttribute('hidden', '');
}
});
In the server, render the datatable as normal and you can remove the toggling (unless you need additional things to happen when the button is clicked).
Here is the full example.
library(shiny)
shinyApp(
ui = tagList(
tags$main(
id = "main",
tags$h1("Collapsible Table Example"),
actionButton("toggleTable", "Toggle Table"),
tags$div(
id = "tableContainer",
DT::dataTableOutput("table")
)
),
tags$script(
type = "text/javascript",
"
const btn = document.getElementById('toggleTable');
const elem = document.getElementById('tableContainer');
btn.addEventListener('click', function(event) {
if (elem.hasAttribute('hidden')) {
elem.removeAttribute('hidden');
} else {
elem.setAttribute('hidden', '');
}
});
"
)
),
server = function(input, output, session) {
output$table <- DT::renderDataTable({
data.frame(
group = sample(c("A", "B"), 20, replace = TRUE),
x = rnorm(n = 20, mean = 50, sd = 2),
y = rnorm(n = 20, mean = 50, sd = 2)
)
})
}
)
I opted to go with a simple solution, just having one table that renders based on the choice of the radiobuttons. Meaning the if/else is just within the renderDataTable function
library(shiny)
library(DT)
ui <- fluidPage(
radioButtons("Buttons", "CHOOSE!", choices = c("MTCARS", "IRIS")),
DT::dataTableOutput("THETABLE")
)
server <- function(input, output, session) {
output$THETABLE<-DT::renderDataTable({
req(input$Buttons)
if(input$Buttons == "MTCARS") {
DT::datatable(mtcars)
} else {
DT::datatable(iris)
}
})
}
shinyApp(ui, server)
Alternatively, you could use conditional panel, so it shows the table based on the radiobutton selection:
library(shiny)
library(DT)
ui <- fluidPage(
radioButtons("Buttons", "CHOOSE!", choices = c("MTCARS", "IRIS")),
conditionalPanel("input.Buttons == 'MTCARS'",
DT::dataTableOutput("TABLEMTCARS")
),
conditionalPanel("input.Buttons == 'IRIS'",
DT::dataTableOutput("TABLEIRIS"))
)
server <- function(input, output, session) {
output$TABLEMTCARS<-DT::renderDataTable({
DT::datatable(mtcars)
})
output$TABLEIRIS<-DT::renderDataTable({
DT::datatable(iris)
})
}
shinyApp(ui, server)
I have a Shinydashboard with reactive Dygraph boxes. I successfully setup a reactive box title to display the maximum value in the dataset and I'd like to do the same for the Status option. Here's what I've got so far:
ui <- dashboardPage(
dashboardHeader(title = "Sites", disable = TRUE),
dashboardSidebar(
#collapsed = TRUE,
disable = TRUE,
sidebarMenu()
),
dashboardBody(
fluidRow(
box(title = textOutput('dyermax'), background = "black", status = textOutput('dyerStat'), dygraphOutput("plot1", height = 173))
)
)
)
The title works as expected but the status gives an error: status can only be "primary", "success", "info", "warning", or "danger".
server <- function(input, output, session) {
#reactivePoll code for importing CSV data (datap)
renderTable(datap())
#Plot1
output$plot1 <- renderDygraph({
dyersburgp <- xts(x = datap()$dyersburg, order.by = datap()$date)
dyersburgf <- xts(x = datap()$dyersburg.1, order.by = datap()$date)
dyersburgmain <- cbind(dyersburgf, dyersburgp)
output$dyermax <- renderPrint({
cat("Dyersburg (max:", max(dyersburgp, na.rm = TRUE),"ug/m3)")
})
dyersburgMx <- max(dyersburgp, na.rm = TRUE)
output$dyerStat <- renderPrint({
if(dyersburgMx >60)("danger" else "info")
})
dygraph(dyersburgmain)
})
}
shinyApp(ui, server)
I would prefer to use the Color option instead of the Status option, but adding "color = "red"" to the box doesn't change the color at all for some reason.
Background
This is actually a really good question. To my understanding, the reason textOutput doesn't work is that, by default, text is rendered within an HTML div. So instead of just passing the raw string ('danger', 'info', etc.), it is rendered as raw HTML. For example, if we inspect the textOutput element in our browser when we run the following,
output$my_text <- renderText({
'this is some text'
})
textOutput('my_text')
we can see it actually renders the below HTML, rather than just "this is some text".
<div id="my_text" class="shiny-text-output shiny-bound-output">this is some text</div>
Obviously this is for a very good reason, and enables us to make good-looking Shiny apps without having to worry about any HTML. But it means we have to be careful when passing outputs as arguments to UI functions.
Solution
There may be better ways to do this, but one way would be creating the HTML yourself by using renderUI/uiOutput, and using the HTML function in combination with paste0 to dynamically render out HTML string to be read directly by uiOutput (which is an alias for the more descriptive htmlOutput). This example changes the status of the box when the user changes the numericInput to above 60, and allows the user to change the title of the box as well. Extend this as required for your own project.
library(shiny)
library(shinydashboard)
body <-
dashboardBody(
fluidRow(
numericInput(
inputId = 'status_input',
label = 'numeric input',
value = 50),
textInput(
inputId = 'box_title',
label = 'box title',
value = ''),
uiOutput('my_box')
)
)
server <- function(input, output, session) {
# get box status as string representing html element
box_status <- reactive({
if (input$status_input > 60) {
'box-danger'
} else {
'box-info'
}
})
# get user input for box title
box_title <- reactive({
input$box_title
})
# generate html to display reactive box
output$my_box <- renderUI({
status <- box_status()
title <- box_title()
# generate the dynamic HTML string
HTML(paste0("
'
<div class=\"box box-solid ", status, "\">
<div class=\"box-header\">
<h3 class=\"box-title\">", title, "</h3>
</div>
<div class=\"box-body\">
Box content!
</div>
</div>
'"
))
})
}
shinyApp(ui = dashboardPage(dashboardHeader(), dashboardSidebar(),body), server)
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.
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)
I've been trying to generate a Shiny page where elements can be added or removed by the user - based on this snippet. I tried to adapt it for UI elements rather than buttons, but I've clearly done something wrong.
I've only included the add-button part here - each time the "add" button is clicked, the correct number of UI elements are added, but they seem to all be the same element rather just appending a new one to the existing list (the number in the label is the same, whereas it should be different for each one) and the initial text values are erased (or, potentially, are just the "default" from the final element repeated however many times).
How can I just add a new UI element and keep any text input that the user has entered in the already-existing elements?
Code:
library(shiny)
# function to render UI element
renderUiElement <- function(id, nameDef) {
renderUI(box(
actionButton(paste0("rmBtn", id), label = "", icon = icon("times")),
# text initial value supplied as a variable
textInput(paste0("nameText", id), label = paste("Item", id), value = nameDef)
))
}
server <- function(input, output, session) {
rv <- reactiveValues(uiComponents = list(), uiIds = list(),
nameVals = list(), lastId = 0)
# on "Add" button click:
observe({
if(is.null(input$addBtn) || input$addBtn == 0) return()
isolate({
# store current text inputs and append new default text
rv$nameVals <- lapply(rv$uiIds, function(x) input[[paste0("nameText", x)]])
rv$nameVals <- append(rv$nameVals, "New Text")
# create (and store) new id number and add it to the list of ids in use
rv$lastId <- rv$lastId + 1 # get the new code to use for these UI elements
rv$uiIds <- append(rv$uiIds, rv$lastId)
# for each id, render the UI element using the appropriate default text
for(i in seq_along(rv$uiIds)) {
thisId <- rv$uiIds[[i]]
output[[paste0("uiEl", thisId)]] <- renderUiElement(
id = thisId, nameDef = rv$nameVals[[i]]
)
}
# add the new UI element into the reactiveValues list
rv$uiComponents <- append(
rv$uiComponents,
list(
list(
uiOutput(paste0("uiEl", rv$lastId)),
br(),br()
)
)
)
})
})
# render all the UI elements inside the container
output$container <- renderUI({
rv$uiComponents
})
}
ui <- fluidPage(
titlePanel("Dynamically Add/Remove UI Elements"),
hr(),
actionButton("addBtn", "Add"),
br(),br(),
uiOutput("container")
)
shinyApp(ui, server)
Hi to dynamicly add objects shiny has the function insertUI.
Your code would look like something similar to this
library(shiny)
library(shinydashboard)
# function to render UI element
renderUiElement <- function(id, nameDef) {
box(
actionButton(paste0("rmBtn", id), label = "", icon = icon("times")),
# text initial value supplied as a variable
textInput(paste0("nameText", id), label = paste("Item", id), value = nameDef)
)
}
server <- function(input, output, session) {
# on "Add" button click:
counter <- 0
observeEvent(
input$addBtn,
{
counter <<- counter +1
insertUI(
selector = '#container',
where = "beforeEnd",
ui = renderUiElement(
id = paste0("ui",counter),
nameDef = "New Text"
),
session = session
)
},
ignoreNULL = TRUE,
ignoreInit = TRUE
)
output$container <- renderUI({
div()
})
}
ui <- fluidPage(
titlePanel("Dynamically Add/Remove UI Elements"),
hr(),
actionButton("addBtn", "Add"),
br(),br(),
uiOutput("container")
)
shinyApp(ui, server)
Hope it helps.