I would like to know how to select all the check-boxes at once. In my code I have Five check-boxes.
server <- function(input, output) {
output$distPlot <- renderPlot({
hist(rnorm(input$obs), col = 'darkgray', border = 'white')
})
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("obs", "Number of observations:", min = 10, max = 500, value = 100),
checkboxInput("checkbox1", label = "meanSNR", value= FALSE),
checkboxInput("checkbox2", label = "t-statistics", value = FALSE),
checkboxInput("checkbox3", label = "adjusted p-value", value = FALSE),
checkboxInput("checkbox4", label = "log-odds", value = FALSE),
checkboxInput("checkbox5", label = "All", value = FALSE)),
mainPanel(plotOutput("distPlot"))
)
)
shinyApp(ui = ui, server = server)
I would like to know how to make it work
1) If the user selects the fifth check-box All, It should automatically select all the check-boxes. On uncheck, it should deselect all the Checkboxes.
2 ) If the user selects the first four check-boxes, it should select the fifth one All check-box too.
For condition 1) , the screen should like this
This isn't nearly as elegant as Jorel's answer, but it's a solution that uses pure shiny package code.
library(shiny)
#* make sure to include session as an argument in order to use the update functions
server <- function(input, output, session) {
output$distPlot <- renderPlot({
hist(rnorm(input$obs), col = 'darkgray', border = 'white')
})
#* This observer will update checkboxes 1 - 4 to TRUE whenever checkbox 5 is TRUE
observeEvent(
eventExpr = input$checkbox5,
handlerExpr =
{
if (input$checkbox5)
lapply(paste0("checkbox", 1:4),
function(x)
{
updateCheckboxInput(session, x, value = input$checkbox5)
}
)
}
)
#* This observer will set checkbox 5 to FALSE whenever any of checkbox 1-4 is FALSE
lapply(paste0("checkbox", 1:4),
function(x)
{
observeEvent(
eventExpr = input[[x]],
handlerExpr =
{
if (!input[[x]]) updateCheckboxInput(session, "checkbox5", value = FALSE)
}
)
}
)
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("obs", "Number of observations:", min = 10, max = 500, value = 100),
checkboxInput("checkbox1", label = "meanSNR", value= FALSE),
checkboxInput("checkbox2", label = "t-statistics", value = FALSE),
checkboxInput("checkbox3", label = "adjusted p-value", value = FALSE),
checkboxInput("checkbox4", label = "log-odds", value = FALSE),
checkboxInput("checkbox5", label = "All", value = FALSE)
),
mainPanel(plotOutput("distPlot"))
)
)
shinyApp(ui = ui, server = server)
Some follow up and recommendations
I spent a little time trying to get the application to do what you've specified, but honestly, it felt pretty unnatural (and wasn't working particularly well).
In a checkbox, if you check "All", it implies that you wish to check all the boxes, but I don't think unselecting "All" necessarily implies unselecting all of the boxes.
Stemming from 1), you're trying to have one control do two different things, which can open the door to confusion.
So here's my recommendation: User four checkboxes and two buttons. The two buttons control if you select all or unselect all of the boxes, and they act independently.
library(shiny)
#* make sure to include session as an argument in order to use the update functions
server <- function(input, output, session) {
output$distPlot <- renderPlot({
hist(rnorm(input$obs), col = 'darkgray', border = 'white')
})
#* This observer will update checkboxes 1 - 4 to TRUE whenever selectAll is clicked
observeEvent(
eventExpr = input$selectAll,
handlerExpr =
{
lapply(paste0("checkbox", 1:4),
function(x)
{
updateCheckboxInput(session = session,
inputId = x,
value = TRUE)
}
)
}
)
#* This observer will update checkboxes 1 - 4 to FALSE whenever deselectAll is clicked
observeEvent(
eventExpr = input$deselectAll,
handlerExpr =
{
lapply(paste0("checkbox", 1:4),
function(x)
{
updateCheckboxInput(session = session,
inputId = x,
value = FALSE)
}
)
}
)
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("obs", "Number of observations:", min = 10, max = 500, value = 100),
checkboxInput("checkbox1", label = "meanSNR", value= FALSE),
checkboxInput("checkbox2", label = "t-statistics", value = FALSE),
checkboxInput("checkbox3", label = "adjusted p-value", value = FALSE),
checkboxInput("checkbox4", label = "log-odds", value = FALSE),
actionButton("selectAll", label = "Select All"),
actionButton("deselectAll", label = "Deselect All")
),
mainPanel(plotOutput("distPlot"))
)
)
shinyApp(ui = ui, server = server)
I'd do this on JS side. I'd get every checkBox like this
var cbx1 = document.getElementById('checkbox1'); etc. and i'll store them in an array
i'll also have a function that will check everything :
checkEverything = function(){
cbx1.val = "true";
cbx2.val = "true";
// etc..
}
And i would bind this function on the 4th checkbox onclick event. I'd also have a function that check if every is checked like :
checkIfEverythingChecked = function(){
if(cbx1.val == true && cbx2.val == true)
cbx4.val = true;
}
And i'd bing this on the onclick event of every checkBox
Related
This is a follow-up to this Dynamic change in vtree within shiny: How to deselect
With this code below, I try to switch the arguments prunesmaller and prunebigger of the vtree package. I am quite sure to do this with an if else but I am not able to fix it:
In general I want to know how to tweak any argument of any function depending on a radiobutton in r shiny:
Here is my code so far:
library(shiny)
library(vtree)
# Define UI ----
ui <- pageWithSidebar(
# App title ----
headerPanel("Cyl vtree"),
# Sidebar panel for inputs ----
sidebarPanel(
radioButtons("smaller_bigger", h3("Prune smaller or bigger?"), choices = c("smaller", "bigger"), inline = TRUE),
sliderInput(inputId = "prune", label = "Number to prune?", step = 10, min = 0, max = 100, value = 0),
selectizeInput("level", label = "Level", choices = NULL, multiple=TRUE),
# This line is the only change from the original code
selectizeInput("values", label= "Values", choices = NULL, multiple=TRUE),
),
# Main panel for displaying outputs ----
mainPanel(
vtreeOutput("VTREE")
)
)
# Define server logic to plot ----
server <- function(input, output,session) {
df <- reactiveVal(mtcars)
vector <- c("cyl","vs", "am","gear")
observe({
updateSelectizeInput(session, "level", choices = colnames(df()[vector]), selected = NULL)
updateSelectizeInput(session, "values", choices = unique(df()$cyl))
})
output[["VTREE"]] <- renderVtree({
vtree(df(), c(input$level),
sameline = TRUE,
follow=list(cyl=input$values),
if(input$smaller_bigger=="smaller"){
prunesmaller = input$prune
} else
(input$smaller_bigger == "bigger"){
prunebigger = input$prune
}
)
})
}
shinyApp(ui, server)
In essence I try to handle this part of the code:
if(input$smaller_bigger=="smaller"){
prunesmaller = input$prune
} else
(input$smaller_bigger == "bigger"){
prunebigger = input$prune
}
)
It should do:
If radiobutton smaller is choosen then the argument should be prunesmaller == input$prune (where input$prune comes from the sliderinput)
If I replace the if else part by prunesmaller = input$prune the code works but only with prunesmaller:
The way you use the if will not work to set the functions argument. Instead use a single if for each argument, e.g. prunesmaller = if (input$smaller_bigger == "smaller") input$prune.
Note: Maybe I missed something, but I got an error when trying to set prunebigger and according to the docs there is no prunebigger argument.
library(shiny)
library(vtree)
# Define UI ----
ui <- pageWithSidebar(
# App title ----
headerPanel("Cyl vtree"),
# Sidebar panel for inputs ----
sidebarPanel(
radioButtons("smaller_bigger", h3("Prune smaller or bigger?"), choices = c("smaller", "bigger"), inline = TRUE),
sliderInput(inputId = "prune", label = "Number to prune?", step = 10, min = 0, max = 100, value = 0),
selectizeInput("level", label = "Level", choices = NULL, multiple = TRUE),
# This line is the only change from the original code
selectizeInput("values", label = "Values", choices = NULL, multiple = TRUE),
),
# Main panel for displaying outputs ----
mainPanel(
vtreeOutput("VTREE")
)
)
# Define server logic to plot ----
server <- function(input, output, session) {
df <- reactiveVal(mtcars)
vector <- c("cyl", "vs", "am", "gear")
observe({
updateSelectizeInput(session, "level", choices = colnames(df()[vector]), selected = NULL)
updateSelectizeInput(session, "values", choices = unique(df()$cyl))
})
output[["VTREE"]] <- renderVtree({
vtree(df(), c(input$level),
sameline = TRUE,
follow = list(cyl = input$values),
prunesmaller = if (input$smaller_bigger == "smaller") input$prune
#prunebigger = if (input$smaller_bigger == "bigger") input$prune
)
})
}
shinyApp(ui, server)
I would like that when user exits the selectizeInput field (clicks outside of selectizeInput), a new option is created and selected (option createOnBlur = TRUE), but I can't figure out how to control the created values to ensure they belong to the "choices" list.
In fact, I would like createOnBlur=TRUE working with create=FALSE, but this obviously doesn't work..
I have looked at selectize.js documentation and I think createFilter and/or onBlur() options could be useful but I didn't succeed in implementing it for my purpose.
Here is a reprex with an age input, I would like that when user tape e.g. "40" and then clik outside of input without pressing "Enter" (ie onBlur), the value 40 is recorded in the input, but if the user tape e.g "444", this impossible age value is not created in the list of choices :
library(shiny)
input_age <- function(mina = 0, maxa =100){
selectizeInput(inputId = "age",
label = "Age",
choices = c("choose one" = "", mina:maxa),
options = list(create = TRUE,
createOnBlur = TRUE)
)
}
ui <- shinyUI(fluidPage(
titlePanel("selectize createonblur"),
mainPanel(
input_age(mina = 20, maxa = 70)
)
))
# SERVER
server <- shinyServer(function(input, output) {
})
shinyApp(ui, server)
You can use updateSelectizeInput to check the selection made against the choices after each interaction with your input.
Please see the following:
library(shiny)
input_age <- function(mina = 0, maxa = 100){
selectizeInput(inputId = "age",
label = "Age",
choices = c("choose one" = "", mina:maxa),
options = list(create = TRUE,
createOnBlur = TRUE)
)
}
minAge <- 20
maxAge <- 70
ui <- shinyUI(fluidPage(
titlePanel("selectize createonblur"),
mainPanel(
input_age(mina = minAge, maxa = maxAge)
)
))
# SERVER
server <- shinyServer(function(input, output, session) {
observeEvent(req(input$age), {
if(length(setdiff(input$age, as.character(seq(minAge, maxAge)))) > 0){
updateSelectizeInput(session,
inputId = "age",
choices = seq(minAge, maxAge),
selected = "")
}
})
})
shinyApp(ui, server)
Update - Here is a JS approach:
library(shiny)
input_age <- function(mina = 0, maxa = 100){
selectizeInput(inputId = "age",
label = "Age",
choices = c("choose one" = "", mina:maxa),
options = list(create = TRUE,
createOnBlur = TRUE))
}
ui <- shinyUI(fluidPage(
tags$head(tags$script(HTML("
$(document).on('shiny:inputchanged', function(event) {
if (event.name === 'age') {
if (isNaN(parseInt(event.value)) || event.value > 70 || event.value < 20) {
var $select = $('#age').selectize();
var selectize = $select[0].selectize;
selectize.setValue(null, true);
}
}
});
"))),
titlePanel("selectize createonblur"),
mainPanel(
input_age(mina = 20, maxa = 70)
)
))
# SERVER
server <- shinyServer(function(input, output, session) {
})
shinyApp(ui, server)
You can supply a regular expression to the createFilter option. If the user types something which doesn't match this regular expression, then "Add ..." will not appear and it will not be possible to add this item.
library(shiny)
ui <- fluidPage(
titlePanel("selectize createonblur"),
mainPanel(
selectizeInput(
inputId = "age",
label = "Age",
choices = c("choose one" = "", 20:70),
options = list(
create = TRUE,
createOnBlur = TRUE,
createFilter = I("/^([2-6][0-9]|70)$/")
)
)
)
)
server <- function(input, output, session) {}
shinyApp(ui, server)
Consider the following shiny app:
library('shiny')
# User Interface/UI
ui <- fluidPage(
titlePanel(
'Slider and Text input update'
), # titlePanel
mainPanel(
# Slider input
sliderInput(
inputId = 'sliderValue',
label = 'Slider value',
min = 0,
max = 1000,
value = 500
), # sliderInput
# Text input
textInput(
inputId = 'textValue',
label = NULL
) # textInput
) # mainPanel
) # fluidPage
# Server logic
server <- function(input, output, session) {
observe({
# Update vertical depth text box with value of slider
updateTextInput(
session = session,
inputId = 'textValue',
value = input$sliderValue
) # updateTextInput
# updateSliderInput(
# session = session,
# inputId = 'sliderValue',
# value = input$textValue
# ) # updateSliderInput
}) # observe
}
# Run the application
shinyApp(ui = ui, server = server)
It allows the user to change the values of a slider (sliderInput), which updates the text in the text box (textInput):
I want these to work in sync. So, instead of just the above slider > text box interaction, I want the opposite as well: text box > slider.
If you uncomment the updateSliderInput component, the two widgets compete against one another; an update of the one leads to an update of the other which leads to an update of the other, ...
How can this be avoided while still making the two be in sync?
One way to do it would be using observeEvent for each input and adding a condition if(as.numeric(input$textValue) != input$sliderValue). This will help you from the inputs calling each others update functions recursively. Then your app would look something like this:
library('shiny')
# User Interface/UI
ui <- fluidPage(
titlePanel(
'Slider and Text input update'
), # titlePanel
mainPanel(
# Slider input
sliderInput(
inputId = 'sliderValue',
label = 'Slider value',
min = 0,
max = 1000,
value = 500
), # sliderInput
# Text input
textInput(
inputId = 'textValue',
value = 500,
label = NULL
) # textInput
) # mainPanel
) # fluidPage
# Server logic
server <- function(input, output, session)
{
observeEvent(input$textValue,{
if(as.numeric(input$textValue) != input$sliderValue)
{
updateSliderInput(
session = session,
inputId = 'sliderValue',
value = input$textValue
) # updateSliderInput
}#if
})
observeEvent(input$sliderValue,{
if(as.numeric(input$textValue) != input$sliderValue)
{
updateTextInput(
session = session,
inputId = 'textValue',
value = input$sliderValue
) # updateTextInput
}#if
})
}
# Run the application
shinyApp(ui = ui, server = server)
The above code can be modified a bit to fix the issue of application getting closed when the input in the test box is empty
library('shiny')
ui <- fluidPage(titlePanel('Slider and Text input update'),
mainPanel(
sliderInput(
inputId = 'sliderValue',
label = 'Slider value',
min = 0,
max = 1000,
value = 500
),
textInput(
inputId = 'textValue',
value = 500,
label = NULL
)
))
# Server logic
server <- function(input, output, session)
{
observeEvent(input$textValue, {
print(input$textValue)
if ((as.numeric(input$textValue) != input$sliderValue) &
input$textValue != "" & input$sliderValue != "")
{
updateSliderInput(
session = session,
inputId = 'sliderValue',
value = input$textValue
)
} else {
if (input$textValue == "") {
updateSliderInput(session = session,
inputId = 'sliderValue',
value = 0)
}
}
})
observeEvent(input$sliderValue, {
if ((as.numeric(input$textValue) != input$sliderValue) &
input$sliderValue != "" & input$textValue != "")
{
updateTextInput(
session = session,
inputId = 'textValue',
value = input$sliderValue
)
}
})
}
# Run the application
shinyApp(ui = ui, server = server)
I am a little late to this discussion, but I have recently been having similar trouble. Only I was wanting to sync numeric inputs with slider values... Using the latest example posted here, I still had issues with infinite loops. I think that I finally found a solution to the infinite loops. Following on from Tinku's answer, I add a time delay in the part of the code where the numeric input is updated by the slider value. I believe that the slider value updates slightly slower than the numeric input, so if changing the numeric input too fast, the lag can become too much and looping occurs. I add the code below…. I set the lag to 0.3 s, which is good enough to avoid the infinite loops on my computer….
library('shiny')
library('shinyvalidate')
ui <- fluidPage(titlePanel('Slider and numeric input'),
mainPanel(
sliderInput(
inputId = 'sliderValue',
label = 'Slider value',
min = 0,
max = 1000,
value = 500,
step = 5,
),
numericInput(
inputId = 'numericValue',
min = 0,
max = 1000,
value = 500,
step = 5,
label = "Numeric value"
),
actionButton("Set", "Apply"),
textOutput("value") #Paste the 'Set' value
))
# Server logic
server <- function(input, output, session)
{
v <- reactiveValues()
#Register the current time
v$now = Sys.time()
v$when = Sys.time()
#The saved numeric value to use in further functions/plots
v$num = 500 # Initial value
observeEvent(input$numericValue, {
v$when = Sys.time()
req(input$numericValue)
if (input$numericValue != input$sliderValue)
{
updateSliderInput(
session = session,
inputId = 'sliderValue',
value = input$numericValue
)
}
})
observeEvent(input$sliderValue, {
v$now = Sys.time()
req(input$numericValue)
if (input$numericValue != input$sliderValue & v$now - v$when > 0.3) #I found 0.3 s a good lag to give (I believe that the slidervalue updates ~0.25 s slower than numericinput)
{
updateNumericInput(
session = session,
inputId = 'numericValue',
value = input$sliderValue
)
}
})
#Only update the reactive value (v$num) if within the specified numeric range....
isolate(
observeEvent(input$Set, {
i <- InputValidator$new()
i$add_rule("numericValue", sv_required(message = "Number must be provided"))
i$add_rule("numericValue", sv_gte(0))
i$add_rule("numericValue", sv_lte(1000))
i$enable()
req(i$is_valid())
v$num <- input$numericValue #Use this reactive value in further functions/plots.....
output$value <- renderText({paste("The syncronised value is:", v$num)})
})
)
}
# Run the application
shinyApp(ui = ui, server = server)
I want to add to shiny dashboard possibility to save and load filter settings. I imagine that user should have possibility to save many filter settings, gives them names and loads them from the list.
Does anyone know any templates or examples which can be helpful?
I don't know about any templates but you could write your own:
I defined inputs in the first column in the UI.
The default values are initialized when the session is started
After that you can save filter settings with the save button or load them with the load button
Other things to note:
You could save the filter settings to file/db to enable using them between users/sessions.
I ignored saving filters with existing names. Could overwrite it as well.
Code:
library(shiny)
library(shinyjs)
library(dplyr)
ui <- fluidPage(
useShinyjs(),
wellPanel(
fluidRow(
column(4,
sliderInput("sepal_length", label = "Select Sepal length", min = 0, max = 10, value = c(4, 6), step = 0.2),
sliderInput("sepal_width", label = "Select Sepal length", min = 0, max = 10, value = c(4, 6), step = 0.2)
),
column(2,
h4("Save/Load filter settings"),
selectInput("filters", label = "Load filters", choices = NULL),
textInput("name", ""),
actionButton("save", label = "Save"),
actionButton("load", label = "Load")
)
)
),
tableOutput("out")
)
server <- function(input, output, session) {
init <- F
rv <- reactiveValues(filters = NULL)
observeEvent(input$save, ignoreNULL = F, {
if(!init) {
rv$filters <- data.frame(
id = "default",
sepal_length_min = input$sepal_length[1],
sepal_length_max = input$sepal_length[2],
sepal_width_min = input$sepal_width[1],
sepal_width_max = input$sepal_width[2],
stringsAsFactors = F)
init <<- T
} else {
if(input$name == "") shinyjs::alert("Filters should be named!")
else {
if(input$name %in% rv$filters$id) {
shinyjs::alert(sprintf("Cannot save filter: %s already exists", input$name))
} else {
rv$filters <- rbind(rv$filters, c(
id = input$name,
sepal_length_min = input$sepal_length[1],
sepal_length_max = input$sepal_length[2],
sepal_width_min = input$sepal_width[1],
sepal_width_max = input$sepal_width[2]))
}
}
}
updateTextInput(session, "name", value = "")
updateSelectInput(session, "filters", choices = rv$filters$id)
})
observeEvent(input$load, {
selected <- rv$filters %>% filter(id == input$filters)
updateSliderInput(session, "sepal_length", value = c(selected$sepal_length_min, selected$sepal_length_max))
updateSliderInput(session, "sepal_width", value = c(selected$sepal_width_min, selected$sepal_width_max))
})
output$out <- renderTable(iris %>% filter(
between(Sepal.Length, input$sepal_length[1], input$sepal_length[2]),
between(Sepal.Width, input$sepal_width[1], input$sepal_width[2])
))
}
shinyApp(ui, server)
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)