Navbar/Tabset with reactive Panel number but NOT rendering everything - r

This question might seem to be a duplicate, but let me explain why it's not.
I want to create a shiny navbarPage that has fixed elements and a reactive number of tabPanels, that reacts to other input elements. There are many questions about how to create reactive tabsetPanels/navbarPages but they mostly aim for what it has to look like. The most common answer (and the answer i don't seek) is to render the whole navbarPage with updated set of tabPanels. I am aware of that concept and I used it in the code below.
Here is what I want my app to look like:
library(shiny)
runApp(
shinyApp(
ui = shinyUI(
fluidPage(
uiOutput("navPage")
)
),
server = function(input, output, session){
MemoryValue1 <- 1
MemoryValue2 <- 1
makeReactiveBinding("MemoryValue1")
observeEvent(input$button, {
output[[paste0("plot_", input$number)]] <- renderPlot({
hist(rnorm(1000))
})
})
observeEvent(input$insidepanels, {
MemoryValue1 <<- input$insidepanels
})
observeEvent(input$number, {
MemoryValue2 <<- input$number
})
output$navPage <- renderUI({
OutsidePanel1 <- tabPanel("Outside1",
numericInput("insidepanels", label = "Number of panels inside NavMenu", value = isolate(MemoryValue1), step = 1, min = 1),
numericInput("number", label = "Panel to add Output-Element to", value = 1, step = isolate(MemoryValue2), min = 1),
actionButton("button", label = "Add Output-Element")
)
OutsidePanel2 <- tabPanel("Ouside2", "Outside 2")
InsidePanels <- lapply(1:MemoryValue1, function(x){tabPanel(paste0("Inside", x), plotOutput(paste0("plot_", x)))})
do.call(navbarPage, list("Nav", OutsidePanel1, OutsidePanel2, do.call(navbarMenu, c("Menu", InsidePanels))))
})
}
)
)
As you might have seen, it takes a lot of effort to store your input values if they are inside other panels and will be re-rendered = reset all the time. I find this solution to be illegible and slow, because of unnecessary rendering. It also interrupts the user who is clicking through values of input$insidepanels.
What I want the app to be like is that the Outside Panels are fixed and dont re-render. The main problem is that inside shiny, navbarPage on rendering distributes HTML elements to two different locations. Inside the navigation panel and to the body as tab content. That means a-posteori added elements will not be properly embedded.
So far, I have tried to create the navbarPage with custom tags and have dynamic output alter only parts of it. That works pretty well with the navigation panel, but not with tab contents. The reason is that all tabs (their div containers) are listed one after another and as soon as I want to inject multiple at once, I am offthrown by htmlOutput, since it (seemingly) has to have a container and cannot just deliver plain HTML. Thus, all custom tabs are not recongnized properly.
Here my code so far:
library(shiny)
runApp(
shinyApp(
ui = shinyUI(
fluidPage(
tags$nav(class = "navbar navbar-default navbar-static-top", role = "navigation",
tags$div(class = "container",
tags$div(class = "navbar-header",
tags$span(class = "navbar-brand", "Nav")
),
tags$ul(class = "nav navbar-nav",
tags$li(
tags$a(href = "#tab1", "data-toggle" = "tab", "data-value" = "Outside1", "Outside1")
),
tags$li(
tags$a(href = "#tab2", "data-toggle" = "tab", "data-value" = "Outside2", "Outside2")
),
tags$li(class = "dropdown",
tags$a(href = "#", class = "dropdown-toggle", "data-toggle" = "dropdown", "Menu1"),
htmlOutput("dropdownmenu", container = tags$ul, class = "dropdown-menu")
)
)
)
),
tags$div(class = "container-fluid",
tags$div(class = "tab-content", id = "tabContent",
tags$div(class = "tab-pane active", "data-value" = "Outside1", id = "tab1",
numericInput("insidepanels", label = "Number of panels inside NavMenu", value = 1, step = 1, min = 1),
numericInput("number", label = "Panel to add Output-Element to", value = 1, step = 1, min = 1),
actionButton("button", label = "Add Output-Element")
),
tags$div(class = "tab-pane", "data-value" = "Outside2", id = "tab2", "Content 2"),
htmlOutput("tabcontents")
)
)
)
),
server = function(input, output, session){
observeEvent(input$button, {
output[[paste0("plot_", input$number)]] <- renderPlot({
hist(rnorm(1000))
})
})
output$dropdownmenu <- renderUI({
lapply(1:input$insidepanels, function(x){tags$li(tags$a(href = paste0("#tab-menu-", x), "data-toggle" = "tab", "data-value" = paste0("Inside", x), paste("Inside", x)))})
})
output$tabcontents <- renderUI({
tagList(
lapply(1:input$insidepanels, function(x){div(class = "tab-pane", "data-value" = paste("Inside", x), id = paste0("tab-menu-", x), plotOutput(paste0("plot_", x)))})
)
})
}
)
)
Note: I also tried to create HTML with JavaScript-Chunks that is triggered from inside server. This works for simple tab content, but I want my tabPanels to still have shiny output elements. I don't see how I can fit that in with JavaScript. That is why I included the plotOutput content in my code.
Thanks to anybody who can help solve this issue!

Finally came up with an own answer. I hope this can be a useful reference to others who try to understand shiny reactiveness. The answer is JavaScript for custom elements (rebuilding standard shiny elements) and using Shiny.unbindAll() / Shiny.bindAll() to achieve the reactivity.
Code:
runApp(
shinyApp(
ui = shinyUI(
fluidPage(
tags$script('
Shiny.addCustomMessageHandler("createTab",
function(nr){
Shiny.unbindAll();
var dropdownContainer = document.getElementById("dropdown-menu");
var liNode = document.createElement("li");
liNode.setAttribute("id", "dropdown-element-" + nr);
var aNode = document.createElement("a");
aNode.setAttribute("href", "#tab-menu-" + nr);
aNode.setAttribute("data-toggle", "tab");
aNode.setAttribute("data-value", "Inside" + nr);
var textNode = document.createTextNode("Inside " + nr);
aNode.appendChild(textNode);
liNode.appendChild(aNode);
dropdownContainer.appendChild(liNode);
var tabContainer = document.getElementById("tabContent");
var tabNode = document.createElement("div");
tabNode.setAttribute("id", "tab-menu-" + nr);
tabNode.setAttribute("class", "tab-pane");
tabNode.setAttribute("data-value", "Inside" + nr);
var plotNode = document.createElement("div");
plotNode.setAttribute("id", "plot-" + nr);
plotNode.setAttribute("class", "shiny-plot-output");
plotNode.setAttribute("style", "width: 100% ; height: 400px");
tabNode.appendChild(document.createTextNode("Content Inside " + nr));
tabNode.appendChild(plotNode);
tabContainer.appendChild(tabNode);
Shiny.bindAll();
}
);
Shiny.addCustomMessageHandler("deleteTab",
function(nr){
var dropmenuElement = document.getElementById("dropdown-element-" + nr);
dropmenuElement.parentNode.removeChild(dropmenuElement);
var tabElement = document.getElementById("tab-menu-" + nr);
tabElement.parentNode.removeChild(tabElement);
}
);
'),
tags$nav(class = "navbar navbar-default navbar-static-top", role = "navigation",
tags$div(class = "container",
tags$div(class = "navbar-header",
tags$span(class = "navbar-brand", "Nav")
),
tags$ul(class = "nav navbar-nav",
tags$li(
tags$a(href = "#tab1", "data-toggle" = "tab", "data-value" = "Outside1", "Outside1")
),
tags$li(
tags$a(href = "#tab2", "data-toggle" = "tab", "data-value" = "Outside2", "Outside2")
),
tags$li(class = "dropdown",
tags$a(href = "#", class = "dropdown-toggle", "data-toggle" = "dropdown", "Menu1"),
tags$ul(id = "dropdown-menu", class = "dropdown-menu")
)
)
)
),
tags$div(class = "container-fluid",
tags$div(class = "tab-content", id = "tabContent",
tags$div(class = "tab-pane active", "data-value" = "Outside1", id = "tab1",
numericInput("insidepanels", label = "Number of panels inside NavMenu", value = 0, step = 1),
numericInput("number", label = "Panel to add Output-Element to", value = 0, step = 1),
actionButton("button", label = "Add Output-Element")
),
tags$div(class = "tab-pane", "data-value" = "Outside2", id = "tab2", "Content 2")
)
)
)
),
server = function(input, output, session){
allOpenTabs <- NULL
observeEvent(input$insidepanels, {
if(!is.na(input$insidepanels)){
localList <- 0:input$insidepanels
lapply(setdiff(localList, allOpenTabs), function(x){
session$sendCustomMessage(type = "createTab", message = x)
})
lapply(setdiff(allOpenTabs, localList), function(x){
session$sendCustomMessage(type = "deleteTab", message = x)
})
allOpenTabs <<- localList
}
})
observeEvent(input$button, {
output[[paste0("plot-", input$number)]] <- renderPlot({
hist(rnorm(1000))
})
})
}
), launch.browser = TRUE
)
It is basically adding the HTML Elements "by hand" and linking them to shiny listeners.

Related

R Shiny click on table field

I am currently learning R. I have a small project where a timetable is displayed and the user has the option to enter a subject.
After adding the subject to the timetable, it should be possible to click on it to open the modalDialog.
Unfortunately my code does not work. I have tried it here:
observeEvent(input$mytable_cells_selected, {
showModal(modalDialog(
title = "Somewhat important message",
"This is a somewhat important message.",
easyClose = TRUE,
footer = NULL))
})
Can someone help me and tell where my error is?
ui <- fluidPage(
theme = bs_theme(version = 4, bootswatch = "minty"),
titlePanel(h1("My timetable", align = "center" )),
sidebarLayout(
position = c("left"),
sidebarPanel(
width = 4,
selectInput("select1", label = h5("Event:"),
choices = c("math" , "sience", "sport") ,
selected = 1,
width = 400),
actionButton("action", label = "Add")),
mainPanel(
width = 8,
tableOutput('mytable')),
),
)
and server:
server <- function(input, output, session) {
timetable <- reactiveVal(
data.frame(monday = c("","","","",""),
tuesday = c("","","","",""),
wednesday = c("","","","",""),
thursday = c("","","","",""),
friday = c("","","","",""))
)
output$mytable <- renderTable(timetable(),
bordered = TRUE,
spacing = c('l'),
width = "100%",
striped = TRUE,
align = 'c',
rownames = TRUE,
selection = list(target = 'cell'))
observeEvent(input$action, {
tmp <- timetable()
tmp[1, "monday"] <- input$select1
timetable(tmp)
})
observeEvent(input$mytable_cells_selected, {
showModal(modalDialog(
title = "message",
"This is a somewhat important message.",
easyClose = TRUE,
footer = NULL))
})
}
shinyApp(ui, server)
As mentioned in the comment, you can use the DT library. Here is a complete example.
Use dataTableOutput in your ui for your data table.
In server, you can include renderDataTable and customize here. In this case, selection is set for single cells.
You can capture the selection event (or can capture clicked event) with input$my_table_cells_selected. In my version I used an underscore for my_table. This information will include the row and column values of the cell selected.
Note that the DT data table could be editable and allow for other interactivity, depending on your needs.
library(shiny)
library(bslib)
library(DT)
ui <- fluidPage(
theme = bs_theme(version = 4, bootswatch = "minty"),
titlePanel(h1("My timetable", align = "center" )),
sidebarLayout(
position = c("left"),
sidebarPanel(
width = 4,
selectInput("select1", label = h5("Event:"),
choices = c("math" , "sience", "sport") ,
selected = 1,
width = 400),
actionButton("action", label = "Add")),
mainPanel(
width = 8,
dataTableOutput('my_table')
)
)
)
server <- function(input, output, session) {
timetable <- reactiveVal(
data.frame(monday = c("","","","",""),
tuesday = c("","","","",""),
wednesday = c("","","","",""),
thursday = c("","","","",""),
friday = c("","","","",""))
)
output$my_table = renderDataTable(timetable(), selection = list(mode = "single", target = "cell"))
observeEvent(input$action, {
tmp <- timetable()
tmp[1, "monday"] <- input$select1
timetable(tmp)
})
observeEvent(input$my_table_cells_selected, {
req(input$my_table_cells_selected)
showModal(modalDialog(
title = "message",
paste("This is a somewhat important message:",
input$my_table_cells_selected[1],
input$my_table_cells_selected[2]),
easyClose = TRUE,
footer = NULL))
})
}
shinyApp(ui, server)

Shiny button within nested module not showing up; namespace issue?

I'm trying to scaffold together a basic dashboard using the shinymaterial package (https://ericrayanderson.github.io/shinymaterial/) but having a slight issue where my dropdown menus (usually selectInput in regular shiny apps) don't show up in a nested UI module.
There should be two dropdown menus above the "Settings" button in this screenshot:
Here's the code snippet for my scaffolding so far:
library(shiny)
library(shinymaterial)
# Wrap shinymaterial apps in material_page
ui <- material_page(
title = "App Title",
nav_bar_fixed = FALSE,
nav_bar_color = "black",
background_color = "white",
# font_color = "black",
# Place side-nav in the beginning of the UI
material_side_nav(
fixed = FALSE,
# Place side-nav tabs within side-nav
material_side_nav_tabs(
side_nav_tabs = c(
"Home" = "home",
"About" = "about"
),
icons = c("home", "help")
),
background_color = "white"
),
# Define side-nav tab content
material_side_nav_tab_content(
side_nav_tab_id = "home",
material_row(
material_column(
material_card(title = NULL,
sidebarCharts("main"),
depth = NULL),
width = 2,
offset = 0
),
material_column(
material_card(title = NULL,
"Chart goes here",
depth = NULL),
width = 10,
offset = 0
)
)
),
material_side_nav_tab_content(
side_nav_tab_id = "about",
tags$h1("About")
)
)
server <- function(input, output, session) {
callModule(chartSettings, "main")
}
# Server modules
chartSettings <- function(input, output, session) {
## 'Home' tab -- Sidebar
output$selectRootSymbol <- renderUI({
.choices <- c('a','b','c')
tagList(
helpText("Root Symbol:"), # Note: helpText() looks a little cleaner versus using the 'label' parameter in selectInput() below
# selectInput(session$ns("reactiveRootSymbol"), label = NULL, choices = .choices, selected = NULL, width = '100%')
material_dropdown(session$ns("reactiveRootSymbol"), label = NULL, choices = .choices, selected = NULL, width = '100%')
)
})
output$selectSymbol <- renderUI({
req(input$reactiveRootSymbol)
.choices <- c('d', 'e', 'f')
tagList(
helpText("Symbol:"),
# selectInput(session$ns("reactiveSymbol"), label = NULL, choices = toupper(.choices), selected = NULL, width = '100%')
material_dropdown(session$ns("reactiveSymbol"), label = NULL, choices = toupper(.choices), selected = NULL, width = '100%')
)
})
}
sidebarCharts <- function(id) {
ns <- NS(id)
tagList(
uiOutput(ns("selectRootSymbol")),
uiOutput(ns("selectSymbol")),
# actionButton(ns("settings"), "Settings", icon = icon("cogs"), width = '100%', class = "btn btn-primary"),p()
material_button(ns("settings"), "Settings", icon = "settings")
)
}
shinyApp(ui = ui, server = server)
I think I have a namespace issue, but I'm not sure (since the button does show up in the nested module). What am I doing wrong?
Any help is much appreciated!
There are at least two issues here.
1. material_dropdown does not display (resolved)
This appears to be due to the unused width = 100% option inside material_dropdown(). Removing this results in some of the drop downs displaying and all of the labels displaying.
2. Consecutive material_dropdown does not display (unresolved)
Having two consecutive material_dropdown's results in only the first drop down displaying, even though both labels display. There have been previous bugs with material_dropdown in the shinymaterial package so this could be part of a related issue.
Here is the code following my exploration:
library(shiny)
library(shinymaterial)
# submodule UI
sidebarCharts <- function(id) {
ns <- NS(id)
tagList(
uiOutput(ns("selectRootSymbol")),
uiOutput(ns("selectSymbol")),
# actionButton(ns("settings"), "Settings", icon = icon("cogs"), width = '100%', class = "btn btn-primary"),p()
material_button(ns("settings"), "Settings", icon = "settings")
)
}
# submodule server
chartSettings <- function(input, output, session) {
## 'Home' tab -- Sidebar
output$selectRootSymbol <- renderUI({
.choices <- c('a','b','c')
material_dropdown(session$ns("reactiveRootSymbol"), label = "Root Symbol:", choices = .choices)
})
output$selectSymbol <- renderUI({
# req(input$reactiveRootSymbol)
.choices <- c('d', 'e', 'f')
material_dropdown(session$ns("reactiveSymbol"), label = "Symbol:", choices = .choices)
})
}
## Wrap shinymaterial apps in material_page ----
ui <- material_page(
title = "App Title",
nav_bar_fixed = FALSE,
nav_bar_color = "black",
background_color = "white",
# font_color = "black",
# Place side-nav in the beginning of the UI
material_side_nav(
fixed = FALSE,
# Place side-nav tabs within side-nav
material_side_nav_tabs(
side_nav_tabs = c(
"Home" = "home"
),
icons = c("home")
),
background_color = "white"
),
# Define side-nav tab content
material_side_nav_tab_content(
side_nav_tab_id = "home",
material_row(
material_column(
material_card(title = NULL,
sidebarCharts("main"),
depth = NULL),
width = 2,
offset = 0
),
material_column(
material_card(title = NULL,
"Chart goes here",
depth = NULL),
width = 10,
offset = 0
)
)
)
)
## main server ----
server <- function(input, output, session) {
callModule(chartSettings, "main")
}
## run ----
shinyApp(ui = ui, server = server)

Updating some but not all elements inside renderUI output

I'm trying to build an interactive UI by rendering it inside an output with renderUI. The thing is: I have inputs created inside this rendering function whose behavior should change according to the answers provided. But when I do it, the reactivity updates the entire output and erases the answers provided, resetting the inputs to original state. Is there a way to determine which inputs I want to update? Or is there a better way of building this structure?
EDIT: Just to clarify: I want to change the label of the textInput without updating the radioButtons. The second radioButton answer should affect the behaviour of the textInput only.
ui <- miniPage(
miniTabstripPanel(id = 'tabs',
miniTabPanel("Data",
miniContentPanel(
selectInput(inputId = 'indicator', label = "Select indicator:",
choices = c('Select an indicator' = 'none',
"Water" = 'iwater',
'Antenatal care 4+ visits' = 'anc4',
'Institutional delivery' = 'ideliv')),
)
),
miniTabPanel("Second tab",
miniContentPanel(
uiOutput(outputId = "indicarea")
)
)
)
)
server <- function(input, output, session) {
iwater_vartype = reactiveVal(value= "Example label 1")
observeEvent(input$iwater_variabletype,{
if (input$iwater_variabletype == 'codes') {
iwater_vartype("Example label 1")
}
else {
iwater_vartype("Example label 2")
}
})
observeEvent(input$indicator,{
output$indicarea = renderUI({
buildUI(input$indicator)
})
})
buildUI = function(indic) {
switch(indic,
'none' = {
h3("Please select an indicator to proceed.")
},
'iwater' = {
tagList(
h3("Improved source of drinking water"),
br(), hr(), br(),
radioButtons(inputId = 'iwater_subsample', label = "Asked it in all?",
choices = c('Yes' = 'yes', 'No' = 'no')),
radioButtons(inputId = 'iwater_variabletype', label = "How was the info collected?",
choices = c('One variable' = 'codes', 'Several variables' = 'variables')),
textInput(inputId = 'iwater_sourcevariable', label= iwater_vartype())
)
},
'anc4' = {
tagList(
textInput(inputId = 'test', label= 'testing')
)
}
)
}
}
runGadget(ui, server)
Thanks in advance!
you could try adding an observeEvent to you server code with something like this.
observeEvent(input$iwater_variabletype,{
updateTextInput(
session = session,
inputId = "iwater_sourcevariable",
label = "new Label for text Input"
)
})
hope this helps!

R Shiny: Isolate dynamic output within dynamic tabs

I am trying to create an app where you choose certain inputs in the sidebar, and when you click on a button it will show the results in a separate tab. I created a tiny example that you can use below.
In this example, you choose 4 letters in the sidebar and if you click on the button, it dynamically creates a separate tab with text output. However, when you change the letters and click on the button again, all previous tabs will update with the new results. I'd like to isolate the result in each tab but I don't know how to do that. I tried to do this by using different output names (see variable summaryname in the server) but it doesn't work.
This example only uses text output, but my real app also uses tables and plots.
I'd appreciate any help!
ui:
ui <- fluidPage(
sidebarLayout(
sidebarPanel(width = 4,
selectInput(inputId = "choice_1", label = "First choice:",
choices = LETTERS, selected = "H", multiple = FALSE),
selectInput(inputId = "choice_2", label = "Second choice:",
choices = LETTERS, selected = "E", multiple = FALSE),
selectInput(inputId = "choice_3", label = "Third choice:",
choices = LETTERS, selected = "L", multiple = FALSE),
selectInput(inputId = "choice_4", label = "Fourth choice:",
choices = LETTERS, selected = "P", multiple = FALSE),
actionButton(inputId = "goButton", label = "Go!")
),
mainPanel(width = 8,
tabPanel("Result", fluid = TRUE,
uiOutput(outputId = "tabs"),
conditionalPanel(condition="input.level == 1",
HTML("<font size = 3><strong>Select your inputs and click 'Go!'.</strong></font>")
),
conditionalPanel(condition="input.level != 1",
uiOutput(outputId = "summary")
)
)
)
)
)
Server:
server <- function(input, output, session){
output$tabs <- renderUI({
Tabs <- as.list(rep(0, input$goButton+1))
for (i in 0:length(Tabs)){
Tabs[i] = lapply(paste("Results", i, sep = " "), tabPanel, value = i)
}
do.call(tabsetPanel, c(Tabs, id = "level"))
})
output$summary <- renderUI({
summary <- eventReactive(input$goButton, {paste("<strong>", "Summary:", "</strong>", "<br>",
"You chose the following letters:", input$choice_1, input$choice_2, input$choice_3, input$choice_4, "." ,"<br>",
"Thank you for helping me!")
})
summaryname <- paste("Summary", input$goButton+1, sep = "")
output[[summaryname]] <- renderText({summary()})
htmlOutput(summaryname)
})
}
EDIT:
I'm experiencing problems now when I try to get a navbarPage layout around the code. Somehow, the results of the dynamic tabs get displayed wrong (and again not isolated properly). I only changed the ui, but I included the server just in case.
ui:
ui <- navbarPage("Shiny",
# Important! : JavaScript functionality to add the Tabs
tags$head(tags$script(HTML("
/* In coherence with the original Shiny way, tab names are created with random numbers.
To avoid duplicate IDs, we collect all generated IDs. */
var hrefCollection = [];
Shiny.addCustomMessageHandler('addTabToTabset', function(message){
var hrefCodes = [];
/* Getting the right tabsetPanel */
var tabsetTarget = document.getElementById(message.tabsetName);
/* Iterating through all Panel elements */
for(var i = 0; i < message.titles.length; i++){
/* Creating 6-digit tab ID and check, whether it was already assigned. */
do {
hrefCodes[i] = Math.floor(Math.random()*100000);
}
while(hrefCollection.indexOf(hrefCodes[i]) != -1);
hrefCollection = hrefCollection.concat(hrefCodes[i]);
/* Creating node in the navigation bar */
var navNode = document.createElement('li');
var linkNode = document.createElement('a');
linkNode.appendChild(document.createTextNode(message.titles[i]));
linkNode.setAttribute('data-toggle', 'tab');
linkNode.setAttribute('data-value', message.titles[i]);
linkNode.setAttribute('href', '#tab-' + hrefCodes[i]);
navNode.appendChild(linkNode);
tabsetTarget.appendChild(navNode);
};
/* Move the tabs content to where they are normally stored. Using timeout, because
it can take some 20-50 millis until the elements are created. */
setTimeout(function(){
var creationPool = document.getElementById('creationPool').childNodes;
var tabContainerTarget = document.getElementsByClassName('tab-content')[0];
/* Again iterate through all Panels. */
for(var i = 0; i < creationPool.length; i++){
var tabContent = creationPool[i];
tabContent.setAttribute('id', 'tab-' + hrefCodes[i]);
tabContainerTarget.appendChild(tabContent);
};
}, 100);
});
"))),
# End Important
tabPanel("Statistics"),
tabPanel("Summary",
sidebarLayout(
sidebarPanel(width = 4,
selectInput(inputId = "choice_1", label = "First choice:",
choices = LETTERS, selected = "H", multiple = FALSE),
selectInput(inputId = "choice_2", label = "Second choice:",
choices = LETTERS, selected = "E", multiple = FALSE),
selectInput(inputId = "choice_3", label = "Third choice:",
choices = LETTERS, selected = "L", multiple = FALSE),
selectInput(inputId = "choice_4", label = "Fourth choice:",
choices = LETTERS, selected = "P", multiple = FALSE),
actionButton("goCreate", "Go create a new Tab!")
),
mainPanel(
tabsetPanel(id = "mainTabset",
tabPanel("InitialPanel1", "Some text here to show this is InitialPanel1",
textOutput("creationInfo"),
# Important! : 'Freshly baked' tabs first enter here.
uiOutput("creationPool", style = "display: none;")
# End Important
)
)
)
)
)
)
Server:
server <- function(input, output, session){
# Important! : creationPool should be hidden to avoid elements flashing before they are moved.
# But hidden elements are ignored by shiny, unless this option below is set.
output$creationPool <- renderUI({})
outputOptions(output, "creationPool", suspendWhenHidden = FALSE)
# End Important
# Important! : This is the make-easy wrapper for adding new tabPanels.
addTabToTabset <- function(Panels, tabsetName){
titles <- lapply(Panels, function(Panel){return(Panel$attribs$title)})
Panels <- lapply(Panels, function(Panel){Panel$attribs$title <- NULL; return(Panel)})
output$creationPool <- renderUI({Panels})
session$sendCustomMessage(type = "addTabToTabset", message = list(titles = titles, tabsetName = tabsetName))
}
# End Important
# From here: Just for demonstration
output$creationInfo <- renderText({
paste0("The next tab will be named: Results ", input$goCreate + 1)
})
observeEvent(input$goCreate, {
nr <- input$goCreate
newTabPanels <- list(
tabPanel(paste0("NewTab ", nr),
htmlOutput(paste0("Html_text", nr)),
actionButton(paste0("Button", nr), "Some new button!"),
textOutput(paste0("Text", nr))
)
)
output[[paste0("Html_text", nr)]] <- renderText({
paste("<strong>", "Summary:", "</strong>", "<br>",
"You chose the following letters:", isolate(input$choice_1), isolate(input$choice_2), isolate(input$choice_3), isolate(input$choice_4), "." ,"<br>",
"Thank you for helping me!")
})
addTabToTabset(newTabPanels, "mainTabset")
})
}
Modifying the code given in the link with the code you provided I was able to produce the desired result.
library(shiny)
ui <- shinyUI(fluidPage(
# Important! : JavaScript functionality to add the Tabs
tags$head(tags$script(HTML("
/* In coherence with the original Shiny way, tab names are created with random numbers.
To avoid duplicate IDs, we collect all generated IDs. */
var hrefCollection = [];
Shiny.addCustomMessageHandler('addTabToTabset', function(message){
var hrefCodes = [];
/* Getting the right tabsetPanel */
var tabsetTarget = document.getElementById(message.tabsetName);
/* Iterating through all Panel elements */
for(var i = 0; i < message.titles.length; i++){
/* Creating 6-digit tab ID and check, whether it was already assigned. */
do {
hrefCodes[i] = Math.floor(Math.random()*100000);
}
while(hrefCollection.indexOf(hrefCodes[i]) != -1);
hrefCollection = hrefCollection.concat(hrefCodes[i]);
/* Creating node in the navigation bar */
var navNode = document.createElement('li');
var linkNode = document.createElement('a');
linkNode.appendChild(document.createTextNode(message.titles[i]));
linkNode.setAttribute('data-toggle', 'tab');
linkNode.setAttribute('data-value', message.titles[i]);
linkNode.setAttribute('href', '#tab-' + hrefCodes[i]);
navNode.appendChild(linkNode);
tabsetTarget.appendChild(navNode);
};
/* Move the tabs content to where they are normally stored. Using timeout, because
it can take some 20-50 millis until the elements are created. */
setTimeout(function(){
var creationPool = document.getElementById('creationPool').childNodes;
var tabContainerTarget = document.getElementsByClassName('tab-content')[0];
/* Again iterate through all Panels. */
for(var i = 0; i < creationPool.length; i++){
var tabContent = creationPool[i];
tabContent.setAttribute('id', 'tab-' + hrefCodes[i]);
tabContainerTarget.appendChild(tabContent);
};
}, 100);
});
"))),
# End Important
sidebarLayout(
sidebarPanel(width = 4,
selectInput(inputId = "choice_1", label = "First choice:",
choices = LETTERS, selected = "H", multiple = FALSE),
selectInput(inputId = "choice_2", label = "Second choice:",
choices = LETTERS, selected = "E", multiple = FALSE),
selectInput(inputId = "choice_3", label = "Third choice:",
choices = LETTERS, selected = "L", multiple = FALSE),
selectInput(inputId = "choice_4", label = "Fourth choice:",
choices = LETTERS, selected = "P", multiple = FALSE),
actionButton(inputId = "goCreate", label = "Go!")
),
mainPanel(width = 8,
tabsetPanel(id = "mainTabset",
tabPanel("InitialPanel1", "Some Text here to show this is InitialPanel1")
),
# Important! : 'Freshly baked' tabs first enter here.
uiOutput("creationPool", style = "display: none;")
# End Important
))
))
server <- function(input, output, session){
# Important! : creationPool should be hidden to avoid elements flashing before they are moved.
# But hidden elements are ignored by shiny, unless this option below is set.
output$creationPool <- renderUI({})
outputOptions(output, "creationPool", suspendWhenHidden = FALSE)
# End Important
# Important! : This is the make-easy wrapper for adding new tabPanels.
addTabToTabset <- function(Panels, tabsetName){
titles <- lapply(Panels, function(Panel){return(Panel$attribs$title)})
Panels <- lapply(Panels, function(Panel){Panel$attribs$title <- NULL; return(Panel)})
output$creationPool <- renderUI({Panels})
session$sendCustomMessage(type = "addTabToTabset", message = list(titles = titles, tabsetName = tabsetName))
}
# End Important
# From here: Just for demonstration
output$creationInfo <- renderText({
paste0("The next tab will be named NewTab", input$goCreate + 1)
})
observeEvent(input$goCreate, {
nr <- input$goCreate
newTabPanels <- list(
tabPanel(paste0("Result", nr),
# actionButton(paste0("Button", nr), "Some new button!"),
htmlOutput(paste0("Text", nr))
)
)
output[[paste0("Text", nr)]] <- renderText({
paste("<strong>", "Summary:", "</strong>", "<br>",
"You chose the following letters:", isolate(input$choice_1), isolate(input$choice_2), isolate(input$choice_3), isolate(input$choice_4), "." ,"<br>",
"Thank you for helping me!")
})
addTabToTabset(newTabPanels, "mainTabset")
})
}
shinyApp(ui, server)
Hope this 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