Search code examples
javascriptcssrshinyshinydashboard

How to keep the position of each scroll of each page independent of the scroll modification of other pages in shinydashboard?


I have a shinydashboard app with multiple pages. However, whenever I leave page A and enter page B and then come back to A, the scroll position of A affects the scroll position B.

I would like to avoid this (ie, that the scrolls are independent)?

I could even put some reproducible code, but it would be too big to put it here. So I figured it would be better to insert a stable shinydashboard app inside shinyGallery:

https://ctmm.shinyapps.io/ctmmweb/

Note that the scrolls are not in the previous position when I change pages in the sidebar.

I would like each scroll of each page to be independent of the scroll of others.

I tried to insert some CSS and JS code in my app (a golem) but I couldn't get the scrolls to be independent of each other for each page.

I believe all shinydashboards show this pattern...

Each page was supposed to keep its position, no matter how many pages I entered later and returned to it.

EDIT

library(shiny)
library(shinydashboard)

header <- dashboardHeader(title = "Dashboard", titleWidth = 320)

sidebar <- dashboardSidebar(
  width = 320,
  
  sidebarMenu(
    
    menuItem(
      text = "A1", 
      tabName = "st1"
    ),
    menuItem(
      text = "A2",
      menuSubItem(
        text = "AA1",
        tabName = "nd2"
      ),
      menuSubItem(
        text = "AA2", 
        tabName = "rd3"
      ), 
      menuSubItem(
        text = "AA3", 
        tabName = "th4"
      )
    )
  )
)

body <- dashboardBody(
  
  HTML(
  "<head>
  <style>
  .small-box {
  height: 500px;
  }
  </style>
  </head>"
  ),
  
  tabItems(
    
    tabItem(
      tabName = "st1", 
      
      fluidPage(
        column(
          width = 6, 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow")
        )
      )
    ), 
    
    tabItem(
      tabName = "nd2", 
      
      fluidPage(
        column(
          width = 6, 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow")
        )
      )
    ), 
    
    tabItem(
      tabName = "rd3", 
      
      fluidPage(
        column(
          width = 6, 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow")
        )
      )
    ), 
    
    tabItem(
      tabName = "th4", 
      
      fluidPage(
        column(
          width = 6, 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow"), 
          valueBox(value = 500, subtitle = "stack overflow")
        )
      )
    )
    
  )
  
  
  
)

ui <- dashboardPage(header = header, sidebar, body)

server <- function(input, output) {
  
}

shinyApp(ui, server)

App link.


Solution

  • This script will keep track of the scroll position of each menuitem ("page") and restore the scroll position when you go back to a page that was previously scrolled.

    A video demonstrating the script can viewer here:

    https://vimeo.com/761038827

    // CSS selector to find menu items to track excluding treeview/expandbutton
    var menuItemsSelector = "ul.sidebar-menu li:not(.treeview)";
    
    // Set a delay (ms) when restoring position giving page time to generate contents
    var delayBeforeRestoringPosition = 50;
    
    // ### ### ### ### ###
    
    // Overwrite console.log to prevent other messages from appearing in the log
    // Only messages starting with "*" with go through - example "*Hello" prints only "Hello"
    consolelog = console.log;
    console.log = function(message) { if (message.indexOf("*") == 0) consolelog(message.substring(1)); }
    
    
    
    // Keep track of clicked menuitems and their scroll positions
    var arrMenuPosition = [];
    
    // Keep track of scrolling status
    var isScrolling = false;
    
    // Keep track of current/active menuitem to save scroll position
    var currentMenuItem = null;
    
    // Get text of current menu item
    var getCurrentMenuItemText = () => { return currentMenuItem.textContent.trim(); }
    
    // Handler scroll actions (save and restore)
    var scrollHandler = (save, positionOrElement) => {
        
        if (save)
        {
            // Get new position in window
            var newPosition = positionOrElement;
            
            // Get text of current menuitem which is used to track the menuitem
            let text = getCurrentMenuItemText();
            
            // Save position of current menuitem
            arrMenuPosition[text] = newPosition;
    
            console.log(`*Page '${text}' was set to position ${newPosition}`);      
    
        } else {
            
            // Register current menuitem
            currentMenuItem = positionOrElement;
            
            // Get text of current menuitem which is used to track the menuitem
            let text = getCurrentMenuItemText();
            console.log("*Menu click, new text is " + text);
            
            // Get last known scroll position of current or 0 if not available
            let position = arrMenuPosition[text] ?? 0;
            
            // Scroll window to position
            setTimeout( () => {
                isScrolling = true;
                window.scrollTo(0, position);
            }, delayBeforeRestoringPosition);
            
            console.log("*Scroll window to position " + position);
        }
    }
    
    // Setup
    var setupIndividualScrollers = () => {
        // Get a list of all menuitems
        let menuItems = document.querySelectorAll(menuItemsSelector);
        
        // If the length of the list is 0 then the menuitems are not yet available
        if (menuItems.length == 0)
        {
            // If this script is executed before the menuitems have been generated,
            // wait for them to be available - call this function again in 100 ms
            setTimeout(setupIndividualScrollers, 100);
        } else {
            console.clear();
            console.log(`*Console clear to make output more visible.`);
            
            // Loop through all menuitems
            menuItems.forEach(li => {
                
                // Add event listener for each menuitem
                li.addEventListener("click", function() {
                    
                    // Scroll position based on menu item that was clicked
                    scrollHandler(false, this);
                    
                });
            });
            
            // Set current menuitem to the first menuitem
            currentMenuItem = document.querySelector(menuItemsSelector);
            
            // Add event listener on window for scroll event
            document.addEventListener("scroll", (e) => {
                
                // Save new position if call did not come from menu item change:
                if (isScrolling == false) scrollHandler(true, window.scrollY);
                
                // Reset scroll status
                isScrolling = false;
                
            });
        }
    };
    // Make initial call to function
    setupIndividualScrollers();