shinyBS Modal within checkbox group - r

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)

Related

RShiny: Hiding / Showing a Table based on Radio Buttons

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)

How can i enable disable an action button based on a string in a line in R shiny?

I have created an application which enables the user to run a query and fetch the results from rodbc. By default the query which is getting generated in the TextAreaInput is select * from $tableName. I have an action button 'Go' next to that Input box and when i press go the query gets executed and the result is fetched in the body.
Now, I want the 'Go' button to be enabled only when the select query has 'when' clause in it. In short the user should be able to execute only when they have included a where clause in the query. I tied str_detect and grpl but they don't seem to be working. Amy help is much appriciated.
Regards
body <- dashboardBody(
fluidRow(
width=12,
column(
width=11,
textAreaInput2( inputId = "query",label = "Enter Query", width = "100%")
),
column(
width = 1,
actionButton(inputId = "runQuery", label = "Go",style="width:70px",class="btn-lg"),
class="align-middle",style = "height:100px;text-align:center;margin-top:25px;"
),style = "height:100px"
),
dataTableOutput('table'),
fluidRow(width = 12, column(
width=10)
)
ui <- dashboardPage(skin = "green", header,sidebar, body)
server <- function(input, output, session) {
resultTable <- data.frame()
observeEvent(input$tableName, {
nQuery = paste("SELECT * FROM ",input$tableName,sep = "" )
updateTextAreaInput(session, "query", value = nQuery)
})
observeEvent(input$runQuery, {
resultTable <<- sqlQuery (channel = conn, query = input$query)
output$table <- renderDataTable(resultTable)}
)
shinyjs can enable/disable buttons for us. grepl can tell us if a string contains another string. The below minimal example shows this in action:
library(shiny)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
textAreaInput("txt_query", label = "Enter Query", width = "100%"),
actionButton("btn_run", label = "Run Query")
)
server <- function(input, output, session) {
#Observe the query text box (runs when the query text box changes)
observeEvent(input$txt_query, {
#Check if the query contains the word 'when'
if (grepl("when", input$txt_query) == TRUE) {
#Enable the run button
shinyjs::enable("btn_run")
} else {
#Disable the run button
shinyjs::disable("btn_run")
}
})
}
shinyApp(ui, server)

R shiny - insertUI()'s "immediate" argument does not work with images

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)

Dynamically add/remove whole UI elements in Shiny

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.

Tooltip in shiny UI for help text

I want to place a help text for check-box label as a tooltip.
In the following example I use the shinyBS package - but I only get it to work for the title of the checkbox input group.
Any ideas how it could work after the "Lernerfolg" or "Enthusiasmus" labels?
library(shiny)
library(shinyBS)
server <- function(input, output) {
output$distPlot <- renderPlot({
hist(rnorm(input$obs), col = 'darkgray', border = 'white')
output$rendered <- renderUI({
checkboxGroupInput("qualdim", tags$span("Auswahl der Qualitätsdimension",
tipify(bsButton("pB2", "?", style = "inverse", size = "extra-small"),
"Here, I can place some help")),
c("Lernerfolg" = "Lernerfolg" ,
"Enthusiasmus" = "Enthusiasmus"
),
selected = c("Lernerfolg"))
})
})
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("obs", "Number of observations:", min = 10, max = 500, value = 100),
uiOutput("rendered")
),
mainPanel(plotOutput("distPlot"))
)
)
shinyApp(ui = ui, server = server)
Sadly, this is one of these moments, where shiny hides most of the construction, which makes it hard to get what you want into the right places.
But like most of the time, some JavaScript will do the trick. I wrote you a function that inserts the bsButton in the right place and calls a shinyBS function to insert the tooltip. (I mainly reconstructed what tipify and bdButton would have done.) With the function you can modifify your tooltip easily without further knowledge about JavaScript.
If you'd like to know more of the details, just ask in comments.
Note: When you refer to the checkbox, use the value of it (the value that is sent to input$qualdim)
library(shiny)
library(shinyBS)
server <- function(input, output) {
makeCheckboxTooltip <- function(checkboxValue, buttonLabel, Tooltip){
script <- 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 buttonID = 'button_' + Math.floor(Math.random()*1000);
var button = document.createElement('button');
button.setAttribute('id', buttonID);
button.setAttribute('type', 'button');
button.setAttribute('class', 'btn action-button btn-inverse btn-xs');
button.appendChild(document.createTextNode('", buttonLabel, "'));
input.parentElement.parentElement.appendChild(button);
shinyBS.addTooltip(buttonID, \"tooltip\", {\"placement\": \"bottom\", \"trigger\": \"hover\", \"title\": \"", Tooltip, "\"})
};
}
});
")))
htmltools::attachDependencies(script, shinyBS:::shinyBSDep)
}
output$distPlot <- renderPlot({
hist(rnorm(input$obs), col = 'darkgray', border = 'white')
output$rendered <- renderUI({
list(
checkboxGroupInput("qualdim", tags$span("Auswahl der Qualitätsdimension",
tipify(bsButton("pB2", "?", style = "inverse", size = "extra-small"), "Here, I can place some help")),
choices = c("Lernerfolg" = "Lernerfolg", "Enthusiasmus" = "Enthusiasmus"),
selected = c("Lernerfolg")),
makeCheckboxTooltip(checkboxValue = "Lernerfolg", buttonLabel = "?", Tooltip = "Look! I can produce a tooltip!")
)
})
})
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("obs", "Number of observations:", min = 10, max = 500, value = 100),
uiOutput("rendered")
),
mainPanel(plotOutput("distPlot"))
)
)
shinyApp(ui = ui, server = server)
Edit:
Added the ShinyBS Dependencies such that the JavaScript API for shinyBS is loaded into the WebSite. Before, this was (more or less accidentally) happening because of the other call to bsButton.
Edit Nr.2: Much more In-Shiny
So this JavaScript thing is quite nice, but is kinda prone to errors and demands the developer to have some additional language skills.
Here, I present another answer, inspired by #CharlFrancoisMarais , that works only from within R and makes things more integrated than before.
Main things are: An extension function to the checkboxGrouInput that allows for adding any element to each of the Checkbox elements. There, one can freely place the bsButton and tooltips, like you would in normal markup, with all function arguments supported.
Second, an extension to the bsButton to place it right. This is more of a custom thing only for #CharlFrancoisMarais request.
I'd suggest you read the Shiny-element manipulation carefully, because this offers so much customization on R level. I'm kinda exited.
Full Code below:
library(shiny)
library(shinyBS)
extendedCheckboxGroup <- function(..., extensions = list()) {
cbg <- checkboxGroupInput(...)
nExtensions <- length(extensions)
nChoices <- length(cbg$children[[2]]$children[[1]])
if (nExtensions > 0 && nChoices > 0) {
lapply(1:min(nExtensions, nChoices), function(i) {
# For each Extension, add the element as a child (to one of the checkboxes)
cbg$children[[2]]$children[[1]][[i]]$children[[2]] <<- extensions[[i]]
})
}
cbg
}
bsButtonRight <- function(...) {
btn <- bsButton(...)
# Directly inject the style into the shiny element.
btn$attribs$style <- "float: right;"
btn
}
server <- function(input, output) {
output$distPlot <- renderPlot({
hist(rnorm(input$obs), col = 'darkgray', border = 'white')
output$rendered <- renderUI({
extendedCheckboxGroup("qualdim", label = "Checkbox", choiceNames = c("cb1", "cb2"), choiceValues = c("check1", "check2"), selected = c("check2"),
extensions = list(
tipify(bsButtonRight("pB1", "?", style = "inverse", size = "extra-small"),
"Here, I can place some help"),
tipify(bsButtonRight("pB2", "?", style = "inverse", size = "extra-small"),
"Here, I can place some other help")
))
})
})
}
ui <- fluidPage(
shinyjs::useShinyjs(),
tags$head(HTML("<script type='text/javascript' src='sbs/shinyBS.js'></script>")),
# useShinyBS
sidebarLayout(
sidebarPanel(
sliderInput("obs", "Number of observations:", min = 10, max = 500, value = 100),
uiOutput("rendered")
),
mainPanel(plotOutput("distPlot"))
)
)
shinyApp(ui = ui, server = server)
Here is slight change - to add tooltips only to the checkboxes.
library(shiny)
library(shinyBS)
server <- function(input, output) {
makeCheckboxTooltip <- function(checkboxValue, buttonLabel, buttonId, Tooltip){
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, "' && input.getAttribute('value') != 'null') {
var button = document.createElement('button');
button.setAttribute('id', '", buttonId, "');
button.setAttribute('type', 'button');
button.setAttribute('class', 'btn action-button btn-inverse btn-xs');
button.style.float = 'right';
button.appendChild(document.createTextNode('", buttonLabel, "'));
input.parentElement.parentElement.appendChild(button);
shinyBS.addTooltip('", buttonId, "', \"tooltip\", {\"placement\": \"right\", \"trigger\": \"click\", \"title\": \"", Tooltip, "\"})
};
}
});
")))
}
output$distPlot <- renderPlot({
hist(rnorm(input$obs), col = 'darkgray', border = 'white')
output$rendered <- renderUI({
checkboxGroupInput("qualdim",
label = "Checkbox",
choiceNames = c("cb1", "cb2"),
choiceValues = c("check1", "check2"),
selected = c("check2"))
})
output$tooltips <- renderUI({
list(
makeCheckboxTooltip(checkboxValue = "check1", buttonLabel = "?", buttonId = "btn1", Tooltip = "tt1!"),
makeCheckboxTooltip(checkboxValue = "check2", buttonLabel = "?", buttonId = "btn2", Tooltip = "tt2!")
)
})
})
}
ui <- fluidPage(
shinyjs::useShinyjs(),
tags$head(HTML("<script type='text/javascript' src='sbs/shinyBS.js'></script>")),
# useShinyBS
sidebarLayout(
sidebarPanel(
sliderInput("obs", "Number of observations:", min = 10, max = 500, value = 100),
uiOutput("rendered"),
uiOutput("tooltips")
),
mainPanel(plotOutput("distPlot"))
)
)
shinyApp(ui = ui, server = server)

Resources