2

I am trying to highlight elements of my bsModal with the r wrapper for intro.js, however cannot get it to work. I have also tried to include custom js scripts, but my js is terrible.

I have also set up multiple different tests hoping it would snag onto something, however it seem like intro.js cannot find the modal's div or any any of the of the elements inside it. I am using rintrojs

Here are some example of people getting it to work in javascript:

But I personally don't know Javascript well enough to integrate a custom solution myself. I've already tried :(

Here's a link to an example I've hosted with the issue: https://arun-sharma.shinyapps.io/introjs/

Does anyone know how I can get the following dummy example to work?

library(rintrojs)
library(shiny)
library(shinydashboard)


intro_df <- data.frame(element = c('#plot_box', '#bttn2', '#box', '#modal'),
                       intro = c('test plot_box', 'test bttn2', 'test box', 'test modal'))

ui <- shinyUI(fluidPage(
    introjsUI(), 
    mainPanel(
        bsModal('modal', '', '', uiOutput('plot_box'), size = 'large'),
        actionButton("bttn", "Start intro")
    )))
server <- shinyServer(function(input, output, session) {

    output$plot <- renderPlot({
        plot(rnorm(50))
    })

    output$plot_box <- renderUI({
        box(id = 'box',
            actionButton('bttn2', 'dummy'),
            plotOutput('plot'), width = '100%'
        )
    })

    observeEvent(input$bttn,{

        toggleModal(session, 'modal', toggle = 'toggle')
        introjs(session, options = list(steps = intro_df))
    })

})
shinyApp(ui = ui, server = server)
Sharma
  • 178
  • 1
  • 3
  • 15

2 Answers2

1

Ultimately, I think this request could make for some useful features in the rintrojs library. In any case, your problems are two-fold:

  1. introjs should not fire until the modal is available in the HTML. The easiest way to do this is to use a button within the modal to fire the tutorial. If you want it to be automatic, you will need some JavaScript that waits until the Modal is ready before firing.
  2. introjs wants to grey out the background and highlight the current item in the tutorial. This means it needs to "interleave" with the modal children. Because the modal is its own stacking context, introjs needs to be fired from within the modal to look at modal children. If you want to look at the entire modal, then it is sufficient to fire introjs from the parent. This functionality does not seem to be in the rintrojs package yet, but is in the JavaScript library.

In order to accomplish #1, I added a JavaScript function to fire introjs on Modal load (after a configurable delay for HTML elements to load). This requires the shinyjs package. Notice the introJs(modal_id), this ensures that the tutorial fires within the modal. In pure JavaScript, it would be introJs('#modal'):

 run_introjs_on_modal_up <- function(
      modal_id
      , input_data
      , wait
    ) {
      runjs(
        paste0(
          "$('"
          , modal_id
          , "').on('shown.bs.modal', function(e) {
          setTimeout(function(){
            introJs('", modal_id, "').addSteps("
          , jsonlite::toJSON(input_data, auto_unbox=TRUE)
          , ").start()
          }, ", wait, ")
          })"
        )
      )
    }

I also added a simple helper for closing the introjs tutorial when navigating away from the modal.

introjs_exit <- function(){
  runjs("introJs().exit()")
}

There was also a single line of CSS necessary to fix the modal-backdrop from getting over-eager and taking over the DOM:

.modal-backdrop { z-index: -10;}

And a (large / not minimal) working example with multiple modals.

library(rintrojs)
library(shiny)
library(shinydashboard)
library(shinyBS)
library(shinyjs)

intro_df <- data.frame(element = c('#plot_box', '#bttn2', '#box', '#modal'),
                       intro = c('test plot_box', 'test bttn2', 'test box', 'test modal'))

intro_df2 <- data.frame(element = c('#plot_box2'),
                       intro = c('test plot_box'))

    run_introjs_on_modal_up <- function(
      modal_id
      , input_data
      , wait
    ) {
      runjs(
        paste0(
          "$('"
          , modal_id
          , "').on('shown.bs.modal', function(e) {
          setTimeout(function(){
            introJs('", modal_id, "').addSteps("
          , jsonlite::toJSON(input_data, auto_unbox=TRUE)
          , ").start()
          }, ", wait, ")
          })"
        )
      )
    }

    introjs_exit <- function(){
      runjs("introJs().exit()")
    }

ui <- shinyUI(fluidPage(
  useShinyjs(),
  tags$head(tags$style(".modal-backdrop { z-index: -10;}")),
  introjsUI(), 
  mainPanel(
    bsModal('modal', '', '', uiOutput('plot_box'), size = 'large'),
    bsModal('modalblah', '', '', uiOutput('plot_box2'), size = 'large'),
    actionButton("bttn", "Start intro")
  )))
server <- shinyServer(function(input, output, session) {
  output$plot <- renderPlot({
    plot(rnorm(50))
  })
  output$plot2 <- renderPlot({
    plot(rnorm(50))
  })

  output$plot_box <- renderUI({
    box(id = 'box',
        actionButton('bttn2', 'dummy'),
        plotOutput('plot'), width = '100%'
    )
  })
  output$plot_box2 <- renderUI({
    box(id = 'box2',
        plotOutput('plot2'), width = '100%'
    )
  })

    run_introjs_on_modal_up("#modal",intro_df, 1000)
    run_introjs_on_modal_up("#modalblah",intro_df2, 1000)

  observeEvent(input$bttn,{

    toggleModal(session, 'modal', toggle = 'toggle')
  })

  observeEvent(input$bttn2, {
    toggleModal(session, 'modal', toggle = 'toggle')
    introjs_exit()

    toggleModal(session, 'modalblah', toggle = 'toggle')
  })

})
shinyApp(ui = ui, server = server)
cole
  • 1,737
  • 2
  • 15
  • 21
0

I was able to fix the problem by adding

.introjs-fixParent.modal {
  z-index:1050 !important;  
}

to my CSS.

Working example:

library(rintrojs)
library(shiny)
library(shinydashboard)
library(shinyBS)
library(shinyjs)

intro_df <- data.frame(element = c('#plot_box', '#bttn2_intro', '#box', '#plot', '#shiny-modal'),
                       intro = c('test plot_box', 'test bttn2', 'test box', 'plot', 'test modal'))

ui <- shinyUI(fluidPage(
  dashboardPage(dashboardHeader(title = "Test"),
                dashboardSidebar(sidebarMenu(menuItem("item1", tabName = "item1"))),
                dashboardBody(tabItems(tabItem("item1", actionButton("bttn", "start intro"))))),
  useShinyjs(),
  tags$head(tags$style(".introjs-fixParent.modal {
                          z-index:1050 !important;  
                        }")),
  introjsUI()
  ))

server <- shinyServer(function(input, output, session) {
  output$plot <- renderPlot({
    plot(rnorm(50))
  })

  output$plot_box <- renderUI({
    box(id = 'box',
        div(id = "bttn2_intro", actionButton('bttn2', 'dummy')),
        plotOutput('plot'), width = '100%'
    )
  })

  observeEvent(input$bttn,{
    showModal(modalDialog(uiOutput('plot_box')))
  })

  observeEvent(input$bttn2, {
    introjs(session, options = list(steps = intro_df))
  })

})
shinyApp(ui = ui, server = server)
Lukas
  • 1