10

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)
sammerk
  • 1,143
  • 1
  • 9
  • 23

2 Answers2

12

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)
K. Rohde
  • 9,439
  • 1
  • 31
  • 51
  • Thank´s, that works great! I will try to adapt it to a `makeCheckboxModal` function (with zero JavaScript knowledge:-). There I can place bigger helptexts or graphs ... Thank`s again! – sammerk Apr 19 '16 at 05:02
  • My modifications for `bsModal()` do not work so I asked a [corresponding question](http://stackoverflow.com/questions/37521669/shinybs-modal-within-checkbox-group) – sammerk May 30 '16 at 09:05
  • Hi, this is great, I have just 2 requests. I dont want the tooltip at the top (next to "Auswahl der Qualitätsdimension") and I also want the tooltips to be right alligned. Thus all tooltips should be on the far right of the checkboxes - how should I proceed with this? – Charl Francois Marais Sep 13 '17 at 16:28
  • @CharlFrancoisMarais Part 2 is easy. Just add the line `button.style.setProperty('float', 'right');` below the lines with `setAttribute` inside the JavaScript. For Part 1: My Script workaround was not recognized by Shiny to be related to ShinyBS. Thus, if you leave out the real Tooltip, no Dependency to ShinyBS is made and the required JavaScript API from ShinyBS is not loaded. I edited the answer to correct this. Now the other Tooltip can be just left out. – K. Rohde Sep 14 '17 at 08:07
  • @K.Rohde I found the answer (see my answer below). I am stuck with one big problem though. I would ideally like to add the tooltip in the `checkboxGroupInput` function this will make my code much more efficient. Do you know how to do this> – Charl Francois Marais Sep 19 '17 at 13:58
  • @CharlFrancoisMarais Done, I guess. Check out the second Edit. – K. Rohde Sep 20 '17 at 08:41
  • @K.Rohde beautiful... I cried a little inside when I saw this :). Really awsome job! Thanks so much! – Charl Francois Marais Sep 20 '17 at 11:53
  • Just one last thing - can you please add a link to the element manipulation read that you recommend? – Charl Francois Marais Sep 20 '17 at 11:55
  • @CharlFrancoisMarais Sorry to disappoint you, but I kinda figured it out myself. I recommend examining elements in the console. They all can be created in a normal R session. And they each have attributes that are similar to the html elements they represent. From there its more or less trial and error. – K. Rohde Sep 20 '17 at 15:30
  • @K.Rohde I see, I just thought that you might have such a read in mind. It works perfectly though! – Charl Francois Marais Sep 20 '17 at 15:34
3

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)