rintrojs icon indicating copy to clipboard operation
rintrojs copied to clipboard

Issue with dynamic server-side steps' tooltip browser placement

Open leonawicz opened this issue 7 years ago • 21 comments

Hi,

Are there any known issues with this dynamic approach and perhaps using shiny dashboard? I am having trouble uncovering why some types of widgets don't seem to get the proper div wrapper (if that is in fact the issue) when using this approach.

I have created a simplified example, which suggests the problem does not have to do with environments, modules, unique dashboard functions like tabBox, etc.

Here is a github gist and here is a live app demo. The plot intro is fine but the selectInput and sliderInput intros show up in the corner, I think because they are not getting wrapped in new divs as they would if I were using introBox() in ui.R instead.

My session info:

R version 3.3.2 (2016-10-31)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows >= 8 x64 (build 9200)

locale:
[1] LC_COLLATE=English_United States.1252  LC_CTYPE=English_United States.1252    LC_MONETARY=English_United States.1252 LC_NUMERIC=C                          
[5] LC_TIME=English_United States.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] rintrojs_0.1.2       shinydashboard_0.5.3 shiny_1.0.0         

loaded via a namespace (and not attached):
 [1] R6_2.2.0        htmltools_0.3.5 tools_3.3.2     Rcpp_0.12.9     jsonlite_1.2    digest_0.6.12   xtable_1.8-2    Cairo_1.5-9     httpuv_1.3.3    mime_0.5

Thanks for checking into this, and thank you for this awesome package, Matt

leonawicz avatar Feb 01 '17 23:02 leonawicz

Hi Matt,

Thanks for the bug report. I will investigate this issue.

Regards, Carl

carlganz avatar Feb 01 '17 23:02 carlganz

As per @wch, "DOM element with the specified ID isn't necessarily the thing that's displayed", so that means you will need to use a different CSS selector for sliderInput and selectInput to work. Below is an altered version of your program that should show you what you need:

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

ui <- dashboardPage(
  dashboardHeader(title = "rintrojs test"),
  dashboardSidebar(introjsUI(),
                   sidebarMenu(menuItem("Tab #1", tabName = "tab1"))),
  dashboardBody(tabItems(
    tabItem(
      tabName = "tab1",
      plotOutput("plot1"),
      selectInput("select1", "Select", letters[1:2], letters[1]),
      selectInput("select2", "Select", letters[1:2], letters[1]),
      sliderInput("slider1", "Slide", 0, 1, 0, 0.5),
      sliderInput("slider2", "Slide", 0, 1, 0, 0.5),
      actionButton("help", "Take tour")
    )
  )),
  title = "rintrojs test"
)

server <- shinyServer(function(input, output, session) {
  steps <- reactive(data.frame(
    element = c(
      "#plot1",
### this is the part that matters
      ".js-irs-0",
      ".js-irs-1",
      "#select1 + .selectize-control",
      "#select2 + .selectize-control"
    ),
    intro = c(
      "This is a plot.",
      "This is a slider.",
      "This is another slider.",
      "This is a select input.",
      "This is another select input."
    ),
    position = c("bottom", "right", "right",
                 "left", "left")
  ))
  observeEvent(input$help, {
    introjs(session, options = list(steps = steps()))
  })
  output$plot1 <- renderPlot({
    plot(0, 0)
  })
})

shinyApp(ui = ui, server = server)

The sliders contain spans with unique class names based on the order they appear so they can be selected that way. For selectizeInputs, you can select the element with class selectize-contol that appears immediately after the element with id of the selectizeInput.

Let me know if this works for you.

Kind Regards, Carl

carlganz avatar Feb 02 '17 17:02 carlganz

This works very well! Thanks for the quick update. I have found some other issues that lead to intro elements continuing to be displayed in the wrong place and sometimes even highlighting a different steps' element while showing the intro tooltip near the correct element. Once I have a simplified example I can post that here under this tooltip placement problems issue, or would you prefer I create a separate issue since the causes seem like they may be unrelated to the cause here? I can try to make an example by perhaps slightly expanding the example you posted above.

Thanks, Matt

leonawicz avatar Feb 02 '17 19:02 leonawicz

When you get a chance please leave an example here, and I will investigate.

carlganz avatar Feb 02 '17 19:02 carlganz

Hi Carl,

I have a working example I'll paste here. Unfortunately, it was supposed to be the non-working example, but I am having difficultly reproducing the problem. I wrote this up expecting it to do the same thing as the app I was originally experiencing the issue with. But this one works fine. I'm including it anyway because with the comments included you can see where I was going with it. This issue is with the callback function not working right, or maybe I am specifying it wrong, in my attempt to handle the user changing tab box tabs during a tour. But I cannot tell why it is working when my other app is not.

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

ui <- dashboardPage(
  dashboardHeader(title = "rintrojs test"),
  dashboardSidebar(introjsUI(),
                   sidebarMenu(menuItem("Tab #1", tabName = "tab1")),
                   ### Tour button made independent of active tab
                   actionButton("help", "Take tour")),
  dashboardBody(tabItems(
    tabItem(
      tabName = "tab1",
      tabBox( # added tab box with two tab panels
        tabPanel("Tab box tab 1",
          plotOutput("plot1"),
          selectInput("select1", "Select", letters[1:2], letters[1]),
          selectInput("select2", "Select", letters[1:2], letters[1]),
          sliderInput("slider1", "Slide", 0, 1, 0, 0.5),
          sliderInput("slider2", "Slide", 0, 1, 0, 0.5)
        ),
        tabPanel("Tab box tab 2", plotOutput("plot2")), # second panel
        id = "tbox", selected="Tab box tab 1", title="Stand age summaries", width=12, side="right"
      )
    )
  )),
  title = "rintrojs test"
)

server <- shinyServer(function(input, output, session) {
  steps <- reactive(data.frame(
    element = c(
      ### Added three elements
      ### But this makes it possible for user to change tab during tour!
      "#tbox",
      "a[data-value=\"Tab box tab 1\"]",
      "a[data-value=\"Tab box tab 2\"]",
      "#plot1",
      ### this is the part that matters
      ".js-irs-0",
      ".js-irs-1",
      "#select1 + .selectize-control",
      "#select2 + .selectize-control"
    ),
    intro = c(
      "This is overview text about the different tabs.",
      "What does first show?",
      "What does second show? Now onto detailed tour for tab 1...",
      "This is a plot.",
      "This is a slider.",
      "This is another slider.",
      "This is a select input.",
      "This is another select input."
    ),
    position = c("bottom", "left", "left", "bottom", "right", "right",
                 "left", "left")
  ))
  
  stepEquals <- function(i) paste0("this._currentStep==", i-1, collapse=" || ") # helper
  observeEvent(input$help, {
    introjs(session,
            ### separate issue: turning off bullets as a way to cheat this problem
            ### appears to have no effect anyway. BUllets always display.
            options = list(steps = steps(), "showBullets"="false"),
            ### Make sure correct tab box tab panel active
            ### Normally only need for step 0 (if tour launched while on tab panel 2)
            ### But now need for step 0 and 2 in case user changes to tab 2 on tour...
            ### Actually need for all steps because user can go to tab 2 on step 0 or 2,
            ### and from there change to any step using by clicking bullets.
            ### But accessing the bullets is where the below callback breaks down:
            ### E.g., on step 0 or 2, click tab panel 2 to change tabs,
            ### then use bullets in tooltip to jump to later step.
            events=list(
              "onchange" = paste0("if (", stepEquals(1:8), ") {
              $('a[data-value=\"Tab box tab 2\"]').removeClass('active');
              $('a[data-value=\"Tab box tab 1\"]').addClass('active');
              $('a[data-value=\"Tab box tab 1\"]').trigger('click');
              }"))
            )
  })
  output$plot1 <- renderPlot({
    plot(0, 0)
  })
  output$plot2 <- renderPlot({
    plot(0, 0)
  })
})

shinyApp(ui = ui, server = server)

leonawicz avatar Feb 02 '17 20:02 leonawicz

Oh, After comparing the two some more in the process of sharing the other app just now, I think I know what is happening, but not how to solve it. It looks like the widgets which get messed up are those wrapped in conditionalPanel in my ui.R. Those not wrapped in conditional panels are unaffected. Some of my selectInput and sliderInput widgets use conditional panels to allow them to be displayed on one of three or two of three tab box panels. This seems directly related but I am not clear on how to address it yet.

leonawicz avatar Feb 02 '17 21:02 leonawicz

Got it. Replace the tabItem() in the above app.R file we've been working with. Use this:

tabItem(
      tabName = "tab1",
      tabBox( # added tab box with two tab panels
        tabPanel("Tab box tab 1",
          plotOutput("plot1"),
          selectInput("select2", "Select", letters[1:2], letters[1]),
          sliderInput("slider1", "Slide", 0, 1, 0, 0.5),
          sliderInput("slider2", "Slide", 0, 1, 0, 0.5)
        ),
        tabPanel("Tab box tab 2", plotOutput("plot2")), # second panel
        id = "tbox", selected="Tab box tab 1", title="Stand age summaries", width=12, side="right"
      ),
      conditionalPanel("input.tbox == 'Tab box tab 1'", selectInput("select1", "Select", letters[1:2], letters[1]))
    )

I moved one selectInput into a conditional panel outside the tab box, to only display when tab box tab 1 is active. This causes the problem. My other app shows a more robust set of problems, if this turns out to be a nuanced issue. But using this code here creates the simplest reproduction of the problem.

Matt

leonawicz avatar Feb 02 '17 21:02 leonawicz

I will investigate the bullets issue.

What happens to the elements in the conditionalPanel if you tell it not to suspend when hidden?

outputOptions(output, "myObject", suspendWhenHidden = FALSE)

Can you explain exactly what the problem is in the example app? When I run the tour it seems to go through all the steps correctly.

carlganz avatar Feb 02 '17 21:02 carlganz

It will go through steps correctly, but when the tab box title bar or individual tabs are highlighted for a tour step, the user can force a tab change. The callback function will correctly take the tab back to the correct tab when they change steps again. However, the containers in the browser for widgets wrapped in conditional panels like the selectInput I moved just a moment ago in my last post cannot be found.

In our app.R file, if you change that tabItem to use the condition panel, then run the tour, then when you are on a step where you can change tabs, change to tab 2. Then once on tab 2, click the bullet (I think the 7th one?) the one that makes you jump to that selectInput that is inside a conditional panel. It will fail to display correctly even though the callback function to go back to tab one runs as it should.

Let me know if that helps, Matt

leonawicz avatar Feb 02 '17 21:02 leonawicz

Oh I misunderstood you regarding outputOptions. Was thinking about the other app. I will check that now.

leonawicz avatar Feb 02 '17 21:02 leonawicz

Ooohhhh I see.

outputOptions won't work since we are dealing with an input and not an output, but it is essentially the same problem I suspect. I will investigate.

Thanks a lot for the thorough bug report.

carlganz avatar Feb 02 '17 21:02 carlganz

Hmm, that's an input. It shouldn't be suspended. If it is, I am unsure how to force that unless I made all my inputs on the server side

leonawicz avatar Feb 02 '17 21:02 leonawicz

No problem. Thank you for your help. This is an outstanding package. If you need any more testing just let me know.

Regards, Matt

leonawicz avatar Feb 02 '17 21:02 leonawicz

Oh one last thing, which I think makes sense regarding the inputs being suspended. When you get a poorly placed tooltip or incorrectly highlighted widget area, simply resizing your browser window will cause it to update and fix itself. Funny behavior.

leonawicz avatar Feb 02 '17 21:02 leonawicz

Hey Matt,

The issue with the showBullets option not being respected should be fixed now. jsonlite was building the options JSON object as {"showBullets":[false]}, but it needs to be {"showBullets":false}.

I am still working on resolving the other issue.

Regards, Carl

carlganz avatar Feb 17 '17 22:02 carlganz

I can confirm hiding bullets now works as long as I specify as "showBullets"=FALSE, rather than using "false". Thanks for the fixes and updates.

A separate issue that may or may not be related to this Github issue in general: I am noticing that something seems to get messed up with the z-index of the intro tooltips when using the back button or left arrow to go backward through the tour steps, but only when doing so from the final tour step.

If I go backward from any other step in the tour, all earlier steps display properly, just as they had when stepping forward through the tour. But if the user reaches the final tour step and then goes "Back" instead of "Next" or "Done", all prior tooltips appear to highlight plain white rectangles. Some very faint content of widgets may be seen underneath (like with some R plots) but it is essentially whited out as though the tooltip is completely on top of what it is highlighting. Let me know if this is something you observe as well.

I have updated to rintrojs_0.1.2.900

Regards, Matt

leonawicz avatar Feb 22 '17 01:02 leonawicz

I can reproduce the issue. I have a suspicion that both issues are upstream with intro.js, but I haven't had a chance to create a reproducible example in pure JavaScript.

Thanks again for the bug reports. This will all get fixed eventually.

carlganz avatar Feb 22 '17 16:02 carlganz

@leonawicz I sincerely apologize for the delay. I believe the issue is resolved now. Please try out the latest github version, and let me know if you are still having problems.

Kind Regards, Carl

carlganz avatar Jun 27 '17 18:06 carlganz

@carlganz Thanks. It seems resolved. I can no longer reproduce the issue.

I've encountered a new problem after upgrading the package. However, I don't know if it is related for sure. Tour steps work properly over any type of container I set them to highlight, except for leaflet map output widgets. While I've often used leaflet maps in tours previously, now what happens when moving to a tour step that highlights a leaflet map (moving from previous or backward from the next step), is a large dark gray pixelated rectangle blob appears on the screen. It is large enough to encapsulate the combination of highlighted leaflet output widget and accompanying tour step text box. It eventually fades away on its own, slowly and seemingly randomly, but looks really unprofessional while still lingering on the screen. I can't figure out how I introduced this issue so I don't know how easy it is to reproduce. I only noticed it after upgrading the package but I can't tell how it may or may not be related to recent changes.

I've tried wrapping the leaflet map in a separate div with unique id and referencing that for the tour step, among other things. I tried removing many other widgets from the app. All to no avail. I continue to get the strange dark gray artifact on the screen when highlighting a leaflet map. I don't notice anything when I inspect the page in the browser inspection tool either.

To see what I mean, I have this example available for now but I won't be able to keep it live for long so please take a look soon. https://uasnap.shinyapps.io/tour_leaflet_test/ After the map etc. all loads, you will see a tour button appear in the sidebar. Click to launch. It's the third step.

Thanks, Matt

leonawicz avatar Jul 02 '17 18:07 leonawicz

Okay this is interesting. I just realized that the tour step works fine over the leaflet map if I press the "next" or "previous" buttons with my mouse. However, if I press the left or right arrow keys, which I prefer, only this method of tour step navigation leads to the transient gray blob!

I'm now confirming this only happens in Firefox. It does not happen in Chrome. I seem to recall my installation of Firefox doing an auto update recently. I have version 54.0.1.

So this makes more sense, in that it has nothing to do with the package and is probably a bug between Firefox and introjs?

Still, I hope there is something that can be done, because a large fraction of users will use firefox + arrow keys and see a really ugly tour step- to the point that I'd prefer to not ever highlight the map in a tour if that is the case. Matt

leonawicz avatar Jul 02 '17 19:07 leonawicz

Thanks for your feedback. I will try to reduce the leaflet/firefox issue to a MRE and file an issue upstream.

carlganz avatar Jul 03 '17 18:07 carlganz