Context
I'm trying to have a tooltip popup show on click at the info icon, and hide on click at the icon itself or at any other place of the app. Similarly to how the tooltips in the left panel of SO behave.
Tried so far:
- The answer provided by thothal below almost have it since it has the desired behavior on click of icons and outside of them. However, it produces a new unwanted behavior that I would need to get rid of. The unwanted behavior is shown and explained in the EDIT section at the end of the question.
- Borrowed from this SO accepted answer to get the tooltip to show and hide on click of the info icon with
data-trigger="click"
. However, I haven't been able to have it hidden also on click at any other place of the app. - Based on this other SO accepted answer, I was able to make the tooltip show on click of the info icon, and hide on click at any other place apart from the icon. However, I haven't been able to make it hide on second click at the icon itself. The code provided in the cited answer does exactly what needed in that MRE, but not in the actual app. The MRE posted here includes the aspects missing in that previous MRE.
Background
This question is related to this other (also posted by me a few days ago) which has already been successfully answered. However, when trying to implement the solution in the actual app I'm working on, it didn't work fully as expected. Further discussion in comments and chat with the person who gave the answer there, let me realize that the following specific aspects of my app, which where not present in my previous MRE, were relevant in order to have the good answer.
New aspects included in this question:
- The tooltips are created server-side
- The help texts come from a reactive object
- Some help texts include html elements
I'm explicitly clarifying this here, hoping to prevent the answer from being marked as duplicate and closed.
An MRE of my code so far
R Shiny file: myApp.R
library(shiny)
# ==============================================================================
js <- "
$(function () {
// initialize tooltips
$('[data-toggle=tooltip]').tooltip({
html: true
});
// create listener on html (`everywhere`) which will hide (all) tooltips on click
$('html').on('click', function(el) {
$('[data-toggle=tooltip]').tooltip('hide');
});
// create listener on tooltip elements to toggle the tooltip
$('[data-toggle=tooltip]').on('click', function(evt) {
// make sure the click is not bubbling up to <html> to avoid closing it right away
evt.stopPropagation();
// hide all other tooltips to ensure there is at most one tooltip open at a time
$('[data-toggle=tooltip]').not($(this)).tooltip('hide');
$(this).tooltip('show');
});
})
"
# ==============================================================================
# ==============================================================================
ui <- function() {
fluidPage(
br(),br(),
fluidRow(
# Step 1
uiOutput('head_step1'),
# here goes some more ui for step 1
br(),br(),
# Step 2
uiOutput('head_step2'),
# here goes some more ui for step 2
br(),br(),
# Step 3
uiOutput('head_step3')
# here goes some more ui for step 3
)
)
}
# ==============================================================================
# ==============================================================================
# this function just returns the label matching the specified id
getLabel <- function(dbData, id) {
dbData$labelsEnlish[dbData$ids == id]
}
# ==============================================================================
# ==============================================================================
server <- function(input, server, output, session) {
# reactive object containing labels for different languages
# - in the actual app this object is much more complex
# with multiple languages, many other features inside,
# and dependencies on SQL-pulled data.
# - here kept reduced to the relevant things for the MRE.
dbData <- reactiveValues(
labelsEnlish = c("Step 1: Drop a Pin",
"Step 2: Find Public Data",
"Step 3: Load Public Data",
# help texts for step 1
"Click the pin icon",
"and then click on a location on the map.",
# help texts for step 2
"Define the distance and period to query.",
"A query for public sites will be available",
# help text for step 3
"Select the data type(s) to retrieve."
),
ids = c(1, 2, 3, # the three headers
4, 5, # the help texts for step 1
6, 7, # the help texts for step 2
8 # the help text for step 3
)
)
# Step 1 - Header
output$head_step1 <- renderUI({
span(tags$script(HTML(js)),
style = "display:inline-block;",
h4(getLabel(dbData, 1), # 'Step 1: Drop a Pin'
style = "vertical-align: middle; display: inline;"),
span(
`data-toggle` = "tooltip",
`data-placement` = "bottom",
`data-trigger` = "manual",
title = HTML('<p>',
getLabel(dbData, 4), # 'Click the pin icon'
as.character(icon("map-marker-alt")),
getLabel(dbData, 5), # 'and then click on a location on the map.'
'</p>'),
icon("info-circle")
)
)
})
# Step 2 - Header
output$head_step2 <- renderUI({
span(tags$script(HTML(js)),
style = "display:inline-block;",
h4(getLabel(dbData, 2), # 'Step 2: Find Public Data'
style = "vertical-align: middle; display: inline;"),
span(
`data-toggle` = "tooltip",
`data-placement` = "bottom",
`data-trigger` = "manual",
title = HTML(
'<div><p>',
getLabel(dbData, 6), # 'Define the distance and period to query.'
'</p><p>',
getLabel(dbData, 7), # 'A query for public sites will be available'
'</p></div>'),
icon("info-circle")
)
)
})
# Step 3 - Header
output$head_step3 <- renderUI({
span(tags$script(HTML(js)),
style = "display:inline-block;",
h4(getLabel(dbData, 3), # 'Step 3: Load Public Data'
style = "vertical-align: middle; display: inline;"),
span(
`data-toggle` = "tooltip",
`data-placement` = "bottom",
`data-trigger` = "manual",
title = getLabel(dbData, 8), # 'Select the data type(s) to retrieve.'
icon("info-circle")
)
)
})
}
# ==============================================================================
# ==============================================================================
shinyApp(ui = ui, server = server,
options = list(display.mode = "normal"),
enableBookmarking = "server")
# ==============================================================================
Note: the person in that same answer told me to invoke the JS code only once, not at each tooltip span, however, I only have an idea of how to do that in the UI, not in the Server. Thus, I'm posting here the code with multiple JS calls, hoping the answer could also help me in that regard.
Desired behavior
- Shows on first click on icon
- Hides on second click on icon or on click at any other place
Current behavior
- Shows on first click on icon
- Hides on click at any other place
- Doesn't hide on second click on icon
EDIT
- On March 02, thothal provided a very useful answer to my question. However, it still produces an undesired behavior, which has retained me from marking the answer as correct. In his solution, the tooltips show on click at the info icon, and hide on click at the icon itself or at any other place of the app, BUT, before one of the icons is pressed for the first time, it has an onHover reaction which shows the tooltip as
HTML
code Here's a clip showing the unwanted onHover behavior:
This behavior is not visible in the clip attached to the answer, probably because the clicks on the icons were done very quickly or because the record was made after each icon was pressed a first time. I'm sure that the code provided in the answer gives the unwanted behavior shown above.
In this edit I also simplified my MRE based on the suggestions made by thothal to make the example actually minimal. I removed the
CSS
and replaced the marker icon by one that doesn't need to be downloaded. Hope this helps make the question clearer for anyone interested on it.