1

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. Pretty much as how the tooltips in the left panel of SO behave.

So far I've 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.

I've tried with different combinations of data-trigger types like hover click and focus, but these don't have the desired effect. I've also seen some SO answers (e.g., [1], [2], [3]) out of the specific Shiny context using some JS and jQuery, but I'm not skilled enough in any of the two languages to adapt the solutions to my need.


An MRE of my code so far

CSS file: style.css

.tooltip > .tooltip-inner {
  pointer-events: none;
  background-color: #2355b4;
  color: #FFFFFF;
  border: 1px solid #000000;
  padding: 10px;
  font-size: 25px;
  font-style: italic;
  text-align: justify;
  margin-left: 0;
  max-width: 1000px;
}

JS file: dynam.js

$(function () {
  $('[data-toggle=tooltip]').tooltip()
})

R Shiny file: myApp.R

library(shiny)

ui <- function() {
  fluidPage(
    includeCSS("style.css"),
    includeScript("dynam.js"),
    br(),br(),
    span(
      "Text that might need further explanation",
      span(
        `data-toggle` = "tooltip",
        `data-placement` = "right",
        `data-trigger` = "click",
        title = "Further explanation",
        icon("info-circle")
      )
    )
  )
}

server <- function(input, server, output, session) {}

shinyApp(ui = ui, server = server,
         options = list(display.mode = "normal"),
         enableBookmarking = "server")

Desired behavior

enter image description here

djbetancourt
  • 347
  • 3
  • 11

1 Answers1

2

You need to provide your own show/hide logic like this:

library(shiny)

js <- HTML("
$(function () {
  // initialize tooltips
  $('[data-toggle=tooltip]').tooltip();
  
  // 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('toggle');
  });
  
})")

sp <- span(
   "Text that might need further explanation",
   span(
      `data-toggle` = "tooltip",
      `data-placement` = "right",
      `data-trigger` = "manual", ## make sure to activate own logic via "manual"
      title = "Further explanation",
      icon("info-circle")
   )
)

ui <- function() {
   fluidPage(
      singleton(tags$head(tags$script(js, type = "application/javascript"))),
      sp,
      br(),
      sp
   )
}

server <- function(input, output, session) {}

shinyApp(ui = ui, server = server)

Update

After some discussion in the chat, it turned out that the tooltips will be created server-side in which case the event handler won't get attached properly. In this case we need to add another layer of event delegation to make this work:

library(shiny)

js <- HTML("
$(function () {
  // create listener on html (`everywhere`) which will hide (all) tooltips on click
  $('html').on('click', function(el) {
     $('[data-toggle=tooltip]').tooltip('hide');
  });
  
  // create another listener on html which, however, fires only
  // for tooltups. This delagtion is needed as the tooltips
  // are created dynamically, hence they may not be present at creation time
  $('html').on('click', '[data-toggle=tooltip]', 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('toggle');
  });
  
})")

make_span <- function(title) {
   tagList(
      span(
         title,
         span(
            `data-toggle` = "tooltip",
            `data-placement` = "right",
            `data-trigger` = "manual", ## make sure to activate own logic via "manual"
            title = "Further explanation",
            icon("info-circle")
         )
      ),
      tags$br()
   )
}

ui <- function() {
   fluidPage(
      singleton(tags$head(tags$script(js, type = "application/javascript"))),
      fluidRow(id = "plc"),
      fluidRow(actionButton("add", "add"))
   )
}

server <- function(input, output, session) {
   observeEvent(input$add, {
      insertUI(selector = "#plc", where = "beforeEnd", 
               ui = make_span("Text that might need further explanation"))
   })
}

shinyApp(ui = ui, server = server)
thothal
  • 16,690
  • 3
  • 36
  • 71
  • Seems very interesting, thank you, trying it out tonight! – djbetancourt Feb 23 '23 at 22:51
  • Great, thank you! This type of resource is helping me better understand how to implement diverse behaviors in my app. – djbetancourt Feb 24 '23 at 05:56
  • Hey thothal, for some reason this works perfect in the small MRE above, but has a small undesired behavior when I try it in the real app I'm working on. When I click the icon the first time, the tooltip shows, but then on the second one, it hides and shows again instead of just staying hidden. Any subsequent click on the icon does the same. Thus, one needs to click anywhere else to have it hidden again. I'd love to share the actual app here, but it is a large code belonging to the company I work for. Does my description of the issue give you any hint of possible solutions? – djbetancourt Feb 24 '23 at 09:12
  • My guess is that you left out the `data-trigger` argument in which case the original click handler is firing which interferes with the manual one. – thothal Feb 24 '23 at 09:18
  • I updated the `data-trigger` to `manual` as you indicated. Here's one of the spans with tooltips: https://drive.google.com/file/d/1ZKtYZV9g4Bp9u0QwQF_u7kLaZJNCF1cM/view?usp=sharing – djbetancourt Feb 24 '23 at 09:26
  • I've been searching and trying for about 3 hours a way to check if the tooltip is visible in the jQuery code and if so `$(this).tooltip('toggle');` or `$(this).tooltip('show');`, else `$(this).tooltip('hide');`. Some people tries to verify if the element has the `aria-describedby` attribute, but I wasn't able to make it work. I'd post a new question, but I'm worried it might not have much sense if I cannot upload the real application, and your approach already provides the solution in the MRE. – djbetancourt Feb 24 '23 at 09:32
  • So you are sure that you have `data-trigger` set to manual for **all** tooltips? – thothal Feb 24 '23 at 10:25
  • Yes, totally sure – djbetancourt Feb 24 '23 at 10:30
  • Wait, are you adding the `JS` in your tooltip ``? Maybe more than once? – thothal Feb 24 '23 at 10:49
  • Yes, in every `` (tooltip) I have, I'm adding it. Might that be causing the issue? I'm fairly new putting hands on the JS and CSS part of a Shiny app. During the previous days I've been trying to have the tooltips functioning and the only place I found to make them act as implemented in the JS was inside each ``. Note that I'm generating each outer `` with header, icon (inner ``) and tooltip using `renderUI` in the server, not in the UI. Just in case that is relevant. – djbetancourt Feb 24 '23 at 11:10
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/252105/discussion-between-thothal-and-djbetancourt). – thothal Feb 24 '23 at 12:58
  • I just tried your updated answer. Thank you for taking the time to prepare it. Unfortunarely, I wasn't able to make it work on my end. I think the best, as your suggested, is to post a new question with all the aspects and requirements of my app, which now I know are relevant for the solution. I posted that new question here: https://stackoverflow.com/q/75600230/12372421. Your inputs there would be very much appreciated. – djbetancourt Mar 02 '23 at 03:22