Added scrollbars
This commit is contained in:
@@ -1466,3 +1466,280 @@ function updateTabs(controllerId) {
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Initialize DataGrid with CSS Grid layout + Custom Scrollbars
|
||||
*
|
||||
* Adapted from previous custom scrollbar implementation to work with CSS Grid.
|
||||
* - Grid handles layout (no height calculations needed)
|
||||
* - Custom scrollbars for visual consistency and positioning control
|
||||
* - Vertical scroll: on body container (.dt2-body-container)
|
||||
* - Horizontal scroll: on table (.dt2-table) to scroll header, body, footer together
|
||||
*
|
||||
* @param {string} gridId - The ID of the DataGrid instance
|
||||
*/
|
||||
function initDataGridScrollbars(gridId) {
|
||||
console.debug("initDataGridScrollbars on element " + gridId);
|
||||
|
||||
const wrapper = document.getElementById(`tw_${gridId}`);
|
||||
|
||||
if (!wrapper) {
|
||||
console.error(`DataGrid wrapper "tw_${gridId}" not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const verticalScrollbar = wrapper.querySelector(".dt2-scrollbars-vertical");
|
||||
const verticalWrapper = wrapper.querySelector(".dt2-scrollbars-vertical-wrapper");
|
||||
const horizontalScrollbar = wrapper.querySelector(".dt2-scrollbars-horizontal");
|
||||
const horizontalWrapper = wrapper.querySelector(".dt2-scrollbars-horizontal-wrapper");
|
||||
const bodyContainer = wrapper.querySelector(".dt2-body-container");
|
||||
const table = wrapper.querySelector(".dt2-table");
|
||||
|
||||
if (!verticalScrollbar || !verticalWrapper || !horizontalScrollbar || !horizontalWrapper || !bodyContainer || !table) {
|
||||
console.error("Essential scrollbar or content elements are missing in the datagrid.");
|
||||
return;
|
||||
}
|
||||
|
||||
// OPTIMIZATION: Cache element references to avoid repeated querySelector calls
|
||||
const header = table.querySelector(".dt2-header");
|
||||
const body = table.querySelector(".dt2-body");
|
||||
|
||||
// OPTIMIZATION: RequestAnimationFrame flags to throttle visual updates
|
||||
let rafScheduledVertical = false;
|
||||
let rafScheduledHorizontal = false;
|
||||
|
||||
const computeScrollbarVisibility = () => {
|
||||
// Vertical: check if body content exceeds body container height
|
||||
const isVerticalRequired = bodyContainer.scrollHeight > bodyContainer.clientHeight;
|
||||
|
||||
// Horizontal: check if content width exceeds table width (use cached references)
|
||||
const contentWidth = Math.max(
|
||||
header ? header.scrollWidth : 0,
|
||||
body ? body.scrollWidth : 0
|
||||
);
|
||||
const isHorizontalRequired = contentWidth > table.clientWidth;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
verticalWrapper.style.display = isVerticalRequired ? "block" : "none";
|
||||
horizontalWrapper.style.display = isHorizontalRequired ? "block" : "none";
|
||||
});
|
||||
};
|
||||
|
||||
const computeScrollbarSize = () => {
|
||||
// Vertical scrollbar height
|
||||
const visibleHeight = bodyContainer.clientHeight;
|
||||
const totalHeight = bodyContainer.scrollHeight;
|
||||
const wrapperHeight = verticalWrapper.offsetHeight;
|
||||
|
||||
let scrollbarHeight = 0;
|
||||
if (totalHeight > 0) {
|
||||
scrollbarHeight = (visibleHeight / totalHeight) * wrapperHeight;
|
||||
}
|
||||
|
||||
// Horizontal scrollbar width (use cached references)
|
||||
const visibleWidth = table.clientWidth;
|
||||
const totalWidth = Math.max(
|
||||
header ? header.scrollWidth : 0,
|
||||
body ? body.scrollWidth : 0
|
||||
);
|
||||
const wrapperWidth = horizontalWrapper.offsetWidth;
|
||||
|
||||
let scrollbarWidth = 0;
|
||||
if (totalWidth > 0) {
|
||||
scrollbarWidth = (visibleWidth / totalWidth) * wrapperWidth;
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
verticalScrollbar.style.height = `${scrollbarHeight}px`;
|
||||
horizontalScrollbar.style.width = `${scrollbarWidth}px`;
|
||||
});
|
||||
};
|
||||
|
||||
const updateVerticalScrollbarPosition = () => {
|
||||
const maxScrollTop = bodyContainer.scrollHeight - bodyContainer.clientHeight;
|
||||
const wrapperHeight = verticalWrapper.offsetHeight;
|
||||
|
||||
if (maxScrollTop > 0) {
|
||||
const scrollRatio = wrapperHeight / bodyContainer.scrollHeight;
|
||||
verticalScrollbar.style.top = `${bodyContainer.scrollTop * scrollRatio}px`;
|
||||
}
|
||||
};
|
||||
|
||||
const updateHorizontalScrollbarPosition = () => {
|
||||
// Use cached references
|
||||
const totalWidth = Math.max(
|
||||
header ? header.scrollWidth : 0,
|
||||
body ? body.scrollWidth : 0
|
||||
);
|
||||
const maxScrollLeft = totalWidth - table.clientWidth;
|
||||
const wrapperWidth = horizontalWrapper.offsetWidth;
|
||||
|
||||
if (maxScrollLeft > 0 && totalWidth > 0) {
|
||||
const scrollRatio = wrapperWidth / totalWidth;
|
||||
horizontalScrollbar.style.left = `${table.scrollLeft * scrollRatio}px`;
|
||||
}
|
||||
};
|
||||
|
||||
// Drag management for vertical scrollbar
|
||||
let isDraggingVertical = false;
|
||||
let startYVertical = 0;
|
||||
let pendingVerticalScroll = null;
|
||||
|
||||
verticalScrollbar.addEventListener("mousedown", (e) => {
|
||||
isDraggingVertical = true;
|
||||
startYVertical = e.clientY;
|
||||
document.body.style.userSelect = "none";
|
||||
verticalScrollbar.classList.add("dt2-dragging");
|
||||
});
|
||||
|
||||
document.addEventListener("mousemove", (e) => {
|
||||
if (isDraggingVertical) {
|
||||
const deltaY = e.clientY - startYVertical;
|
||||
startYVertical = e.clientY;
|
||||
|
||||
// OPTIMIZATION: Store the scroll delta, update visual in RAF
|
||||
if (pendingVerticalScroll === null) {
|
||||
pendingVerticalScroll = 0;
|
||||
}
|
||||
pendingVerticalScroll += deltaY;
|
||||
|
||||
if (!rafScheduledVertical) {
|
||||
rafScheduledVertical = true;
|
||||
requestAnimationFrame(() => {
|
||||
rafScheduledVertical = false;
|
||||
|
||||
const wrapperHeight = verticalWrapper.offsetHeight;
|
||||
const scrollbarHeight = verticalScrollbar.offsetHeight;
|
||||
const maxScrollTop = bodyContainer.scrollHeight - bodyContainer.clientHeight;
|
||||
const scrollRatio = maxScrollTop / (wrapperHeight - scrollbarHeight);
|
||||
|
||||
let newTop = parseFloat(verticalScrollbar.style.top || "0") + pendingVerticalScroll;
|
||||
newTop = Math.max(0, Math.min(newTop, wrapperHeight - scrollbarHeight));
|
||||
|
||||
verticalScrollbar.style.top = `${newTop}px`;
|
||||
bodyContainer.scrollTop = newTop * scrollRatio;
|
||||
|
||||
pendingVerticalScroll = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mouseup", () => {
|
||||
if (isDraggingVertical) {
|
||||
isDraggingVertical = false;
|
||||
document.body.style.userSelect = "";
|
||||
verticalScrollbar.classList.remove("dt2-dragging");
|
||||
}
|
||||
});
|
||||
|
||||
// Drag management for horizontal scrollbar
|
||||
let isDraggingHorizontal = false;
|
||||
let startXHorizontal = 0;
|
||||
let pendingHorizontalScroll = null;
|
||||
|
||||
horizontalScrollbar.addEventListener("mousedown", (e) => {
|
||||
isDraggingHorizontal = true;
|
||||
startXHorizontal = e.clientX;
|
||||
document.body.style.userSelect = "none";
|
||||
horizontalScrollbar.classList.add("dt2-dragging");
|
||||
});
|
||||
|
||||
document.addEventListener("mousemove", (e) => {
|
||||
if (isDraggingHorizontal) {
|
||||
const deltaX = e.clientX - startXHorizontal;
|
||||
startXHorizontal = e.clientX;
|
||||
|
||||
// OPTIMIZATION: Store the scroll delta, update visual in RAF
|
||||
if (pendingHorizontalScroll === null) {
|
||||
pendingHorizontalScroll = 0;
|
||||
}
|
||||
pendingHorizontalScroll += deltaX;
|
||||
|
||||
if (!rafScheduledHorizontal) {
|
||||
rafScheduledHorizontal = true;
|
||||
requestAnimationFrame(() => {
|
||||
rafScheduledHorizontal = false;
|
||||
|
||||
const wrapperWidth = horizontalWrapper.offsetWidth;
|
||||
const scrollbarWidth = horizontalScrollbar.offsetWidth;
|
||||
|
||||
// Use cached references
|
||||
const totalWidth = Math.max(
|
||||
header ? header.scrollWidth : 0,
|
||||
body ? body.scrollWidth : 0
|
||||
);
|
||||
const maxScrollLeft = totalWidth - table.clientWidth;
|
||||
const scrollRatio = maxScrollLeft / (wrapperWidth - scrollbarWidth);
|
||||
|
||||
let newLeft = parseFloat(horizontalScrollbar.style.left || "0") + pendingHorizontalScroll;
|
||||
newLeft = Math.max(0, Math.min(newLeft, wrapperWidth - scrollbarWidth));
|
||||
|
||||
horizontalScrollbar.style.left = `${newLeft}px`;
|
||||
table.scrollLeft = newLeft * scrollRatio;
|
||||
|
||||
pendingHorizontalScroll = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mouseup", () => {
|
||||
if (isDraggingHorizontal) {
|
||||
isDraggingHorizontal = false;
|
||||
document.body.style.userSelect = "";
|
||||
horizontalScrollbar.classList.remove("dt2-dragging");
|
||||
}
|
||||
});
|
||||
|
||||
// Wheel scrolling - OPTIMIZED with RAF throttling
|
||||
let rafScheduledWheel = false;
|
||||
let pendingWheelDeltaX = 0;
|
||||
let pendingWheelDeltaY = 0;
|
||||
|
||||
const handleWheelScrolling = (event) => {
|
||||
// Accumulate wheel deltas
|
||||
pendingWheelDeltaX += event.deltaX;
|
||||
pendingWheelDeltaY += event.deltaY;
|
||||
|
||||
// Schedule update in next animation frame (throttle)
|
||||
if (!rafScheduledWheel) {
|
||||
rafScheduledWheel = true;
|
||||
requestAnimationFrame(() => {
|
||||
rafScheduledWheel = false;
|
||||
|
||||
// Apply accumulated scroll
|
||||
bodyContainer.scrollTop += pendingWheelDeltaY;
|
||||
table.scrollLeft += pendingWheelDeltaX;
|
||||
|
||||
// Update scrollbar positions
|
||||
updateVerticalScrollbarPosition();
|
||||
updateHorizontalScrollbarPosition();
|
||||
|
||||
// Reset pending deltas
|
||||
pendingWheelDeltaX = 0;
|
||||
pendingWheelDeltaY = 0;
|
||||
});
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
wrapper.addEventListener("wheel", handleWheelScrolling, { passive: false });
|
||||
|
||||
// Initialize scrollbars
|
||||
computeScrollbarVisibility();
|
||||
computeScrollbarSize();
|
||||
updateVerticalScrollbarPosition();
|
||||
updateHorizontalScrollbarPosition();
|
||||
|
||||
// Recompute on window resize
|
||||
window.addEventListener("resize", () => {
|
||||
computeScrollbarVisibility();
|
||||
computeScrollbarSize();
|
||||
updateVerticalScrollbarPosition();
|
||||
updateHorizontalScrollbarPosition();
|
||||
});
|
||||
|
||||
console.info(`DataGrid "${gridId}" initialized with custom scrollbars`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user