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.
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)
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:
// 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();