Added scrollbars

This commit is contained in:
2026-01-11 23:25:55 +01:00
parent 5d6c02001e
commit d909f2125d
3 changed files with 445 additions and 247 deletions

View File

@@ -690,7 +690,7 @@
.mf-panel-main { .mf-panel-main {
flex: 1; flex: 1;
height: 100%; height: 100%;
overflow: auto; overflow: hidden;
min-width: 0; /* Important to allow the shrinking of flexbox */ min-width: 0; /* Important to allow the shrinking of flexbox */
} }
@@ -829,169 +829,33 @@
/* ********************************************* */ /* ********************************************* */
/* ************* Datagrid Component ************ */ /* ************* Datagrid Component ************ */
/* ********************************************* */ /* ********************************************* */
input:focus {
outline: none;
}
.dt2-drag-drop {
display: none;
position: absolute;
top: 100%;
z-index: var(--datagrid-drag-drop-zindex);
width: 100px;
border: 1px solid var(--color-base-300);
border-radius: 10px;
padding: 10px;
box-shadow: 0 0 40px rgba(0, 0, 0, 0.3);
background: var(--color-base-100);
box-sizing: border-box;
overflow-x: auto;
pointer-events: none; /* Prevent interfering with mouse events */
}
.dt2-main {
height: 100%;
position: relative;
}
.dt2-sidebar {
opacity: 0; /* Default to invisible */
visibility: hidden; /* Prevent interaction when invisible */
transition: opacity 0.3s ease, visibility 0s linear 0.3s; /* Delay visibility removal */
position: absolute;
top: 0;
right: 0;
width: 75%;
max-height: 710px;
overflow-y: auto;
background-color: var(--color-base-100);
z-index: var(--datagrid-sidebar-zindex);
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.5); /* Stronger shadow */
border-radius: 10px;
}
.dt2-sidebar.active {
opacity: 1;
visibility: visible;
transition: opacity 0.3s ease;
}
.dt2-container {
position: relative;
}
.dt2-scrollbars {
position: absolute;
top: 24px;
bottom: 0px;
left: 0;
right: 0;
pointer-events: none; /* Ensures parents don't intercept pointer events */
z-index: var(--datagrid-scrollbars-zindex);
}
/* Scrollbar Wrappers common attributes*/
.dt2-scrollbars-vertical-wrapper,
.dt2-scrollbars-horizontal-wrapper {
position: absolute;
background-color: var(--color-base-200);
opacity: 1;
transition: opacity 0.2s ease-in-out; /* Smooth fade in/out */
pointer-events: auto; /* Allow interaction */
}
/* Scrollbar Wrappers */
.dt2-scrollbars-vertical-wrapper {
left: auto;
right: 3px;
top: 3px;
bottom: 3px;
width: 8px;
}
.dt2-scrollbars-horizontal-wrapper {
left: 3px;
right: 3px;
top: auto;
bottom: -12px;
height: 8px;
}
/* Scrollbars */
.dt2-scrollbars-vertical,
.dt2-scrollbars-horizontal {
background-color: var(--color-base-300);
border-radius: 3px;
pointer-events: auto; /* Allow interaction with the scrollbar */
cursor: pointer;
position: absolute;
border-radius: 3px; /* Rounded corners */
pointer-events: auto; /* Enable interaction */
cursor: pointer;
}
/* Vertical Scrollbar */
.dt2-scrollbars-vertical {
left: 0;
right: 0;
top: auto;
bottom: auto;
width: 100%; /* Fits inside its wrapper */
}
/* Horizontal Scrollbar */
.dt2-scrollbars-horizontal {
left: auto;
right: auto;
top: 0;
bottom: 0;
height: 100%; /* Fits inside its wrapper */
}
/* Scrollbar hover effects */
.dt2-scrollbars-vertical:hover,
.dt2-scrollbars-horizontal:hover,
.dt2-scrollbars-vertical.dt2-dragging,
.dt2-scrollbars-horizontal.dt2-dragging {
background-color: var(--color-base-content);
}
.dt2-table {
--color-border: color-mix(in oklab, var(--color-base-content) 20%, #0000);
--color-resize: color-mix(in oklab, var(--color-base-content) 50%, #0000);
display: flex;
flex-direction: column;
border: 1px solid var(--color-border);
border-radius: 10px;
/* overflow: hidden; */
}
.dt2-table:focus {
outline: none;
}
/* Header and Footer */
.dt2-header, .dt2-header,
.dt2-footer { .dt2-footer {
background-color: var(--color-base-200); background-color: var(--color-base-200);
border-radius: 10px 10px 0 0; border-radius: 10px 10px 0 0;
min-width: max-content; min-width: max-content; /* Content width propagates to scrollable parent */
} }
/* Body */
.dt2-body { .dt2-body {
overflow: hidden; /* You can change this to auto if horizontal scrolling is required */ overflow: hidden;
font-size: 14px; font-size: 14px;
min-width: max-content; min-width: max-content; /* Content width propagates to scrollable parent */
} }
/* Row */
.dt2-row { .dt2-row {
display: flex; display: flex;
width: 100%; width: 100%;
height: 22px; height: 22px;
} }
/* Cell */
.dt2-cell { .dt2-cell {
--color-border: color-mix(in oklab, var(--color-base-content) 20%, #0000);
--color-resize: color-mix(in oklab, var(--color-base-content) 50%, #0000);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
@@ -1002,11 +866,12 @@ input:focus {
min-width: 100px; min-width: 100px;
flex-grow: 0; flex-grow: 0;
flex-shrink: 1; flex-shrink: 1;
box-sizing: border-box; /* to include the borders in the computations */ box-sizing: border-box;
border-bottom: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border);
user-select: none; user-select: none;
} }
/* Cell content types */
.dt2-cell-content-text { .dt2-cell-content-text {
text-align: inherit; text-align: inherit;
width: 100%; width: 100%;
@@ -1016,8 +881,8 @@ input:focus {
.dt2-cell-content-checkbox { .dt2-cell-content-checkbox {
display: flex; display: flex;
width: 100%; width: 100%;
justify-content: center; /* Horizontally center the icon */ justify-content: center;
align-items: center; /* Vertically center the icon */ align-items: center;
} }
.dt2-cell-content-number { .dt2-cell-content-number {
@@ -1026,36 +891,12 @@ input:focus {
padding-right: 10px; padding-right: 10px;
} }
/* Footer cell */
.dt2-footer-cell { .dt2-footer-cell {
cursor: pointer cursor: pointer;
}
.dt2-footer-menu {
position: absolute;
display: None;
z-index: var(--datagrid-menu-zindex);
border: 1px solid oklch(var(--b3));
box-sizing: border-box;
width: 80px;
background-color: var(--color-base-100); /* Add background color */
opacity: 1; /* Ensure full opacity */
}
.dt2-footer-menu.show {
display: block;
}
.dt2-footer-menu-item {
padding: 0 8px;
border-radius: 4px;
background-color: var(--color-base-100); /* Add background color */
}
.dt2-footer-menu-item:hover {
background: color-mix(in oklab, var(--color-base-100, var(--color-base-200)), #000 7%);
cursor: pointer
} }
/* Resize handle */
.dt2-resize-handle { .dt2-resize-handle {
position: absolute; position: absolute;
right: 0; right: 0;
@@ -1066,100 +907,149 @@ input:focus {
} }
.dt2-resize-handle::after { .dt2-resize-handle::after {
content: ''; /* This is required */ content: '';
position: absolute; /* Position as needed */ position: absolute;
z-index: var(--datagrid-resize-zindex); z-index: var(--datagrid-resize-zindex);
display: block; /* Makes it a block element */ display: block;
width: 3px; width: 3px;
height: 60%; height: 60%;
top: calc(50% - 60% * 0.5); top: calc(50% - 60% * 0.5);
background-color: var(--color-resize); background-color: var(--color-resize);
} }
.dt2-header-hidden { /* Hidden column */
width: 5px;
background: var(--color-neutral-content);
border-bottom: 1px solid var(--color-border);
cursor: pointer;
}
.dt2-col-hidden { .dt2-col-hidden {
width: 5px; width: 5px;
border-bottom: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border);
} }
/* Highlight */
.dt2-highlight-1 { .dt2-highlight-1 {
color: var(--color-accent); color: var(--color-accent);
} }
.dt2-item-handle { /* *********************************************** */
background-image: radial-gradient(var(--color-primary-content) 40%, transparent 0); /* ******** DataGrid Fixed Header/Footer ******** */
background-repeat: repeat; /* *********************************************** */
background-size: 4px 4px;
cursor: grab; /*
display: inline-block; * DataGrid with CSS Grid + Custom Scrollbars
height: 16px; * - Wrapper takes 100% of parent height
margin: auto; * - Table uses Grid: header (auto) + body (1fr) + footer (auto)
* - Native scrollbars hidden, custom scrollbars overlaid
* - Vertical scrollbar: right side of entire table
* - Horizontal scrollbar: bottom, under footer
*/
/* Main wrapper - takes full parent height, contains table + scrollbars */
.dt2-table-wrapper {
height: 100%;
overflow: hidden;
position: relative; position: relative;
top: 1px;
width: 12px;
} }
/* **************************************************************************** */ /* Table with Grid layout - horizontal scroll enabled, scrollbars hidden */
/* COLUMNS SETTINGS */ .dt2-table {
/* **************************************************************************** */ height: 100%;
.dt2-cs-header {
background-color: var(--color-base-200);
min-width: max-content;
}
.dt2-cs-columns {
display: grid; display: grid;
grid-template-columns: 20px 1fr 0.5fr 0.5fr 0.5fr 0.5fr; grid-template-rows: auto 1fr auto; /* header, body, footer */
overflow-x: auto; /* Enable horizontal scroll */
overflow-y: hidden; /* No vertical scroll on table */
scrollbar-width: none; /* Firefox: hide scrollbar */
-ms-overflow-style: none; /* IE/Edge: hide scrollbar */
} }
.dt2-cs-body input { /* Chrome/Safari: hide scrollbar */
outline: none; .dt2-table::-webkit-scrollbar {
border-color: transparent; display: none;
box-shadow: none;
} }
.dt2-cs-body input[type="checkbox"], /* Header - no scroll, takes natural height */
.dt2-cs-body input.checkbox { .dt2-header-container {
outline: initial; overflow: hidden;
border-color: var(--color-border); min-width: max-content; /* Force table to be as wide as content */
} }
/* Body - scrollable vertically via JS, scrollbars hidden */
.dt2-cs-cell { .dt2-body-container {
padding: 0 6px 0 6px; overflow: hidden; /* Scrollbars hidden, scroll via JS */
margin: auto; min-height: 0; /* Important for Grid to allow shrinking */
min-width: max-content; /* Force table to be as wide as content */
} }
.dt2-cs-checkbox-cell { /* Footer - no scroll, takes natural height */
margin: auto; .dt2-footer-container {
overflow: hidden;
min-width: max-content; /* Force table to be as wide as content */
} }
.dt2-cs-number-cell { /* Custom scrollbars container - overlaid on table */
padding: 0 6px 0 6px; .dt2-scrollbars {
text-align: right; position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
pointer-events: none; /* Let clicks pass through */
z-index: 10;
} }
.dt2-cs-select-cell { /* Scrollbar wrappers - clickable/draggable */
padding: 0 6px; .dt2-scrollbars-vertical-wrapper,
margin: 3px 0; .dt2-scrollbars-horizontal-wrapper {
position: absolute;
background-color: var(--color-base-200);
opacity: 1;
transition: opacity 0.2s ease-in-out;
pointer-events: auto; /* Enable interaction */
} }
.dt2-cs-body input:hover { /* Vertical scrollbar wrapper - right side, full table height */
border: 1px solid #ccc; /* Provide a subtle border on focus */ .dt2-scrollbars-vertical-wrapper {
right: 0;
top: 0;
bottom: 0;
width: 8px;
} }
/* Horizontal scrollbar wrapper - bottom, full width minus vertical scrollbar */
.dt2-views-container-select { .dt2-scrollbars-horizontal-wrapper {
width: 170px; left: 0;
right: 8px; /* Leave space for vertical scrollbar */
bottom: 0;
height: 8px;
} }
.dt2-views-container-create { /* Scrollbar thumbs */
width: 300px; .dt2-scrollbars-vertical,
.dt2-scrollbars-horizontal {
background-color: color-mix(in oklab, var(--color-base-content) 20%, #0000);
border-radius: 3px;
position: absolute;
cursor: pointer;
transition: background-color 0.2s ease;
}
/* Vertical scrollbar thumb */
.dt2-scrollbars-vertical {
left: 0;
right: 0;
top: 0;
width: 100%;
}
/* Horizontal scrollbar thumb */
.dt2-scrollbars-horizontal {
top: 0;
bottom: 0;
left: 0;
height: 100%;
}
/* Hover and dragging states */
.dt2-scrollbars-vertical:hover,
.dt2-scrollbars-horizontal:hover,
.dt2-scrollbars-vertical.dt2-dragging,
.dt2-scrollbars-horizontal.dt2-dragging {
background-color: color-mix(in oklab, var(--color-base-content) 30%, #0000);
} }

View File

@@ -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`);
}

View File

@@ -281,11 +281,11 @@ class DataGrid(MultipleInstance):
def mk_body(self): def mk_body(self):
return Div( return Div(
*self.mk_body_content_page(0), *self.mk_body_content_page(0),
cls="dt2-body", cls="dt2-body"
id=f"tb_{self._id}",
) )
def mk_footers(self): def mk_footers(self):
return self.mk_headers()
return Div( return Div(
*[Div( *[Div(
*[self.mk_aggregation_cell(col_def, row_index, footer) for col_def in self._state.columns], *[self.mk_aggregation_cell(col_def, row_index, footer) for col_def in self._state.columns],
@@ -299,11 +299,43 @@ class DataGrid(MultipleInstance):
def mk_table(self): def mk_table(self):
return Div( return Div(
# Grid table with header, body, footer
Div(
# Header container - no scroll
Div(
self.mk_headers(), self.mk_headers(),
cls="dt2-header-container"
),
# Body container - scroll via JS, scrollbars hidden
Div(
self.mk_body(), self.mk_body(),
cls="dt2-body-container",
id=f"tb_{self._id}"
),
# Footer container - no scroll
Div(
self.mk_footers(), self.mk_footers(),
cls="dt2-footer-container"
),
cls="dt2-table", cls="dt2-table",
id=f"t_{self._id}", id=f"t_{self._id}"
),
# Custom scrollbars overlaid
Div(
# Vertical scrollbar wrapper (right side)
Div(
Div(cls="dt2-scrollbars-vertical"),
cls="dt2-scrollbars-vertical-wrapper"
),
# Horizontal scrollbar wrapper (bottom)
Div(
Div(cls="dt2-scrollbars-horizontal"),
cls="dt2-scrollbars-horizontal-wrapper"
),
cls="dt2-scrollbars"
),
cls="dt2-table-wrapper",
id=f"tw_{self._id}"
) )
def mk_aggregation_cell(self, col_def, row_index: int, footer_conf, oob=False): def mk_aggregation_cell(self, col_def, row_index: int, footer_conf, oob=False):
@@ -373,11 +405,10 @@ class DataGrid(MultipleInstance):
return Div("No data to display !") return Div("No data to display !")
return Div( return Div(
Div(
self.mk_table(), self.mk_table(),
# Script(f"bindDatagrid('{self._id}', false);"), Script(f"initDataGridScrollbars('{self._id}');"),
), id=self._id,
id=self._id style="height: 100%;"
) )
def __ft__(self): def __ft__(self):