Search code examples
rshinyshinybs

ShinyBS bsPopover and updateSelectInput


I want to add tool tips to an dynamic user interface. When I initialize the UI the tool tips are working fine

selectInput(ns("Main2_1"),"Label","abc",  selectize = TRUE, multiple = TRUE),
bsPopover(ns("Main2_1"), "Label", "content", placement = "left", trigger = "focus"),

but once I use to update the choices of Main2_1 in my server script with

updateSelectInput(session, "Main2_1", choices=foo)

it deletes the tool tip too. Adding a new tool tip with addPopover on the server side does not eliminates the problem


Solution

  • I agree, this is some poor design right there. I can't even tell, why the addPopover won't work. Maybe because the observers don't execute the commands one by one...

    However, there is a way to get to your thing. By rewriting the bsPopover, we can take changes of the corresponding element into account.

    I created an updateResistantPopover function, that adds an additional eventListener (mutationListener) to the element, whos id is given, that re-installs the popover whenever some child of the element changes.

    Example code below:

    library(shiny)
    library(shinyBS)
    
    updateResistantPopover <- function(id, title, content, placement = "bottom", trigger = "hover", options = NULL){
      options = shinyBS:::buildTooltipOrPopoverOptionsList(title, placement, trigger, options, content)
      options = paste0("{'", paste(names(options), options, sep = "': '", collapse = "', '"), "'}") 
      bsTag <- shiny::tags$script(shiny::HTML(paste0("
        $(document).ready(function() {
          var target = document.querySelector('#", id, "');
          var observer = new MutationObserver(function(mutations) {
            setTimeout(function() {
              shinyBS.addTooltip('", id, "', 'popover', ", options, ");
            }, 200);
          });
          observer.observe(target, { childList: true });
        });
      ")))
      htmltools::attachDependencies(bsTag, shinyBS:::shinyBSDep)
    }
    
    ui <- shinyUI(fluidPage(
      selectInput("Main2_1","Label","abc",  selectize = TRUE, multiple = TRUE),
      updateResistantPopover("Main2_1", "Label", "content", placement = "right", trigger = "focus"),
      actionButton("destroy", "destroy!")    
    ))
    
    server <- function(input, output, session){     
      observeEvent(input$destroy, {
        updateSelectInput(session, "Main2_1", choices="foo")
      })
    }
    
    shinyApp(ui, server)