首页 > 解决方案 > Using (r)introjs to automatically highlight sidebar menus is not adjusted correctly

问题描述

I am using the JS-extension rintrojs and extended its feature to unfold the menu sidebar when an sidebar item should be highlighted on the sidebar and the formerly unfolded sidebar submenu collapses. It extends as soon as an sidebar menu is "introduced".

Everything works fine except that the highlighting itself highlights the area where the menu would have been if the former menu would not have been collapsed. enter image description here

I tried some hacks like immediatly going to the next step when a certain step has been reached but to no success. It looks like area to highlight it determined before the JS is triggered.

# shiny
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyjs)
library(rintrojs)

# tidyverse
library(dplyr)
library(tibble)
library(stringi)


# allow IDs to be added to the menu item
menuItemID <- function (text, id = NULL, ..., icon = NULL, badgeLabel = NULL, badgeColor = "green", 
                       tabName = NULL, href = NULL, newtab = TRUE, selected = NULL, 
                       expandedName = as.character(gsub("[[:space:]]", "", text)), 
                       startExpanded = FALSE) 
{
  subItems <- list(...)
  if (!is.null(icon)) 
    shinydashboard:::tagAssert(icon, type = "i")
  if (!is.null(href) + !is.null(tabName) + (length(subItems) > 
                                            0) != 1) {
    stop("Must have either href, tabName, or sub-items (contained in ...).")
  }
  if (!is.null(badgeLabel) && length(subItems) != 0) {
    stop("Can't have both badge and subItems")
  }
  shinydashboard:::validateColor(badgeColor)
  isTabItem <- FALSE
  target <- NULL
  if (!is.null(tabName)) {
    shinydashboard:::validateTabName(tabName)
    isTabItem <- TRUE
    href <- paste0("#shiny-tab-", tabName)
  }
  else if (is.null(href)) {
    href <- "#"
  }
  else {
    if (newtab) 
      target <- "_blank"
  }
  if (!is.null(badgeLabel)) {
    badgeTag <- tags$small(class = paste0("badge pull-right bg-", 
                                          badgeColor), badgeLabel)
  }
  else {
    badgeTag <- NULL
  }
  if (length(subItems) == 0) {
    return(tags$li(id = id, a(href = href, `data-toggle` = if (isTabItem) "tab",
                              `data-value` = if (!is.null(tabName)) tabName, `data-start-selected` = if (isTRUE(selected)) 1 else NULL, 
                              target = target, icon, span(text), badgeTag)))
  }
  default <- if (startExpanded) 
    expandedName
  else ""
  dataExpanded <- shinydashboard:::`%OR%`(shiny::restoreInput(id = "sidebarItemExpanded", 
                                                              default), "")
  isExpanded <- nzchar(dataExpanded) && (dataExpanded == expandedName)
  tags$li(id = id, class = "treeview", a(href = href, icon, span(text), 
                                         shiny::icon("angle-left", class = "pull-right")), do.call(tags$ul, 
                                                                                                   c(class = paste0("treeview-menu", if (isExpanded) " menu-open" else ""), 
                                                                                                     style = paste0("display: ", if (isExpanded) "block;" else "none;"), 
                                                                                                     `data-expanded` = expandedName, subItems)))
}

ui <- shinyUI(
  dashboardPagePlus(
    
    ## Header ====
    header = dashboardHeaderPlus(
      
      enable_rightsidebar = FALSE,
      tags$li(class = "dropdown",
                  actionButton("intro_btn", "Get Started")
              )
      ),
    
    ## Sidebar ====
    sidebar = dashboardSidebar(
      fluidPage(fluidRow(sidebarMenu(
        id = "sidebarmenu",
        menuItemID(
          id = "driv_sidebar",
          text = "Section 1",
          tabName = "driv_sidebar",
          icon = icon("chevron-right", lib = "font-awesome"),
          startExpanded = TRUE,
          div(id = "env-intro", menuSubItem("Tab A", tabName = "env_tab", icon = icon("chevron-right", lib = "font-awesome"))),
          div(id = "vic-intro", menuSubItem("Tab B", tabName = "vic_tab", icon = icon("chevron-right", lib = "font-awesome"))),
          div(id = "gen-intro", menuSubItem("Tab C", tabName = "gen_tab", icon = icon("chevron-right", lib = "font-awesome"))),
          div(id = "pow-intro", menuSubItem("Tab D", tabName = "pow_tab", icon = icon("chevron-right", lib = "font-awesome")))
        ),
        menuItemID(
          id = "hint_sidebar",
          text = "Section 2",
          tabName = "hint_sidebar",
          icon = icon("chevron-right", lib = "font-awesome"),
          div(id = "apps-intro",menuSubItem("Tab X", tabName = "apps_tab", icon = icon("chevron-right", lib = "font-awesome"))),
          div(id = "ent-intro",menuSubItem("Tab Y", tabName = "ent_tab", icon = icon("chevron-right", lib = "font-awesome")))
        )
      )))
      
    ),
    
    ## Body ====
    body = dashboardBody(
      
      id = "dashbody",
      useShinyjs(),
      introjsUI(),
      # SAVE THE JS in the same folder as the file app.R as switch_sidebar.js !!
      tags$head(includeScript("switch_sidebar.js")),
      tags$head(
        tabItems(
          
        )
      )
    )
    
  )
)


server <- shinyServer(function(input, output, session) {
  
  # Intro steps ----------------------------------------------------------------
  intro_guide  <- tibble(step = 1:6,
                         intro = c(stri_c(stri_rand_strings(8, 7), collapse = " "), 
                                   stri_c(stri_rand_strings(8, 7), collapse = " "), 
                                   stri_c(stri_rand_strings(8, 7), collapse = " "), 
                                   stri_c(stri_rand_strings(8, 7), collapse = " "), 
                                   stri_c(stri_rand_strings(8, 7), collapse = " "), 
                                   stri_c(stri_rand_strings(8, 7), collapse = " ")), 
                         element = c("#driv_sidebar", "#gen-intro",
                                     "#pow-intro", "#hint_sidebar", "#apps-intro", "#ent-intro"),
                         position = c("auto", "auto", "auto", "auto", "auto", "auto"))
  
  # INTRO ----------------------------------------------------------------------
  # https://shiny.rstudio.com/articles/js-introjs.html
  observeEvent(input$intro_btn, {
    
    introjs(session = session,
            options = list(steps = intro_guide),
            events = list(onbeforechange = I("rintrojs.callback.switchSidebars(targetElement)")))
    
  })
})


shiny::shinyApp(ui, server)

rintrojs = function() {
  return {
    callback : {
      switchSidebars : function(targetElement) {
        console.log("Correct File");
        // get nodelist of tab panes 
        var tabpanes = document.querySelectorAll("div.tab-pane");
        var selector;
        // iterate over the list of tab panes and click their corresponding link
        // element if the node contains the upcoming target element 
        for (i = 0; i < tabpanes.length; i++) {
          if (tabpanes[i].contains(targetElement)) {
            selector = "a[href = \"#" + tabpanes[i].id + "\"]";
            document.querySelector(selector).click();
          }
        }
        // get nodelist of treeviews 
        var treeview = document.querySelectorAll(".treeview");
        // iterate over the list of treeviews and click their corresponding link
        // element if the node contains the upcoming target element 
        if(treeview.length > 0){
        for (i = 0; i < treeview.length; i++) {
          if (treeview[i].contains(targetElement)) {
          if (document.querySelectorAll("#" + treeview[i].id + " > .treeview-menu")[0].className == "treeview-menu"){ 
            selector = "a[href = \"#" + "shiny-tab-" + treeview[i].id + "\"]";
            document.querySelector(selector).click();
          }
        }}
        }
        
        // hacky try: list an div-id twice and trigger next step right when the first one 
        // has been reached 
        // activate next sidebar menu
        console.log(targetElement);
        let count = Object.keys(window.introJs.instances).length-1;
        let instance = window.introJs.instances[count];
        console.log(instance._currentStep);
        if(instance._currentStep == 4) {
          console.log("Hello Check");
          instance.refresh();
          //document.getElementsByClassName("introjs-button introjs-nextbutton").click();
          //instance.nextStep();
        }
    }
    }
  };
}();

标签: javascriptrshinyintro.js

解决方案


推荐阅读