Improving lazing loading. The scrollbars updates itself
This commit is contained in:
@@ -25,11 +25,19 @@ function bindTooltipsWithDelegation() {
|
|||||||
|
|
||||||
// Add a single mouseenter and mouseleave listener to the parent element
|
// Add a single mouseenter and mouseleave listener to the parent element
|
||||||
element.addEventListener("mouseenter", (event) => {
|
element.addEventListener("mouseenter", (event) => {
|
||||||
|
//console.debug("Entering element", event.target)
|
||||||
|
|
||||||
const cell = event.target.closest("[data-tooltip]");
|
const cell = event.target.closest("[data-tooltip]");
|
||||||
if (!cell) return;
|
if (!cell) {
|
||||||
|
// console.debug(" No 'data-tooltip' attribute found. Stopping.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const no_tooltip = element.hasAttribute("mmt-no-tooltip");
|
const no_tooltip = element.hasAttribute("mmt-no-tooltip");
|
||||||
if (no_tooltip) return;
|
if (no_tooltip) {
|
||||||
|
// console.debug(" Attribute 'mmt-no-tooltip' found. Cancelling.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const content = cell.querySelector(".truncate") || cell;
|
const content = cell.querySelector(".truncate") || cell;
|
||||||
const isOverflowing = content.scrollWidth > content.clientWidth;
|
const isOverflowing = content.scrollWidth > content.clientWidth;
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
function bindDatagrid(datagridId, allowColumnsReordering) {
|
function bindDatagrid(datagridId, allowColumnsReordering) {
|
||||||
bindScrollbars(datagridId);
|
manageScrollbars(datagridId, true);
|
||||||
makeResizable(datagridId)
|
makeResizable(datagridId);
|
||||||
|
|
||||||
document.body.addEventListener('htmx:sseBeforeMessage', function (e) {
|
|
||||||
console.log("htmx:sseBeforeMessage", e)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindScrollbars(datagridId) {
|
function bindScrollbars(datagridId) {
|
||||||
@@ -180,6 +176,224 @@ function bindScrollbars(datagridId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function manageScrollbars(datagridId, binding) {
|
||||||
|
console.debug("manageScrollbars on element " + datagridId + " with binding=" + binding);
|
||||||
|
|
||||||
|
const datagrid = document.getElementById(datagridId);
|
||||||
|
|
||||||
|
if (!datagrid) {
|
||||||
|
console.error(`Datagrid with id "${datagridId}" not found.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const verticalScrollbar = datagrid.querySelector(".dt2-scrollbars-vertical");
|
||||||
|
const verticalWrapper = datagrid.querySelector(".dt2-scrollbars-vertical-wrapper");
|
||||||
|
const horizontalScrollbar = datagrid.querySelector(".dt2-scrollbars-horizontal");
|
||||||
|
const horizontalWrapper = datagrid.querySelector(".dt2-scrollbars-horizontal-wrapper");
|
||||||
|
const body = datagrid.querySelector(".dt2-body");
|
||||||
|
const table = datagrid.querySelector(".dt2-table");
|
||||||
|
|
||||||
|
if (!verticalScrollbar || !verticalWrapper || !horizontalScrollbar || !horizontalWrapper || !body || !table) {
|
||||||
|
console.error("Essential scrollbars or content elements are missing in the datagrid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const computeScrollbarVisibility = () => {
|
||||||
|
// Determine if the content is clipped
|
||||||
|
const isVerticalRequired = body.scrollHeight > body.clientHeight;
|
||||||
|
const isHorizontalRequired = table.scrollWidth > table.clientWidth;
|
||||||
|
|
||||||
|
// Show or hide the scrollbar wrappers
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
verticalWrapper.style.display = isVerticalRequired ? "block" : "none";
|
||||||
|
horizontalWrapper.style.display = isHorizontalRequired ? "block" : "none";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const computeScrollbarSize = () => {
|
||||||
|
// Vertical scrollbar height
|
||||||
|
const visibleHeight = body.clientHeight;
|
||||||
|
const totalHeight = body.scrollHeight;
|
||||||
|
const wrapperHeight = verticalWrapper.offsetHeight;
|
||||||
|
|
||||||
|
let scrollbarHeight = 0;
|
||||||
|
if (totalHeight > 0) {
|
||||||
|
scrollbarHeight = (visibleHeight / totalHeight) * wrapperHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal scrollbar width
|
||||||
|
const visibleWidth = table.clientWidth;
|
||||||
|
const totalWidth = table.scrollWidth;
|
||||||
|
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 updateVerticalScrollbarForMouseWheel = () => {
|
||||||
|
const maxScrollTop = body.scrollHeight - body.clientHeight;
|
||||||
|
const wrapperHeight = verticalWrapper.offsetHeight;
|
||||||
|
|
||||||
|
if (maxScrollTop > 0) {
|
||||||
|
const scrollRatio = wrapperHeight / body.scrollHeight;
|
||||||
|
verticalScrollbar.style.top = `${body.scrollTop * scrollRatio}px`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (binding) {
|
||||||
|
// Clean up existing managers if they exist
|
||||||
|
if (datagrid._managers) {
|
||||||
|
// Remove drag events
|
||||||
|
if (datagrid._managers.dragManager) {
|
||||||
|
verticalScrollbar.removeEventListener("mousedown", datagrid._managers.dragManager.verticalMouseDown);
|
||||||
|
horizontalScrollbar.removeEventListener("mousedown", datagrid._managers.dragManager.horizontalMouseDown);
|
||||||
|
document.removeEventListener("mousemove", datagrid._managers.dragManager.mouseMove);
|
||||||
|
document.removeEventListener("mouseup", datagrid._managers.dragManager.mouseUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove wheel events
|
||||||
|
if (datagrid._managers.wheelManager) {
|
||||||
|
body.removeEventListener("wheel", datagrid._managers.wheelManager.handleWheelScrolling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove resize events
|
||||||
|
if (datagrid._managers.resizeManager) {
|
||||||
|
window.removeEventListener("resize", datagrid._managers.resizeManager.handleResize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create managers
|
||||||
|
const dragManager = {
|
||||||
|
isDragging: false,
|
||||||
|
startY: 0,
|
||||||
|
startX: 0,
|
||||||
|
|
||||||
|
updateVerticalScrollbar: (deltaX, deltaY) => {
|
||||||
|
const wrapperHeight = verticalWrapper.offsetHeight;
|
||||||
|
const scrollbarHeight = verticalScrollbar.offsetHeight;
|
||||||
|
const maxScrollTop = body.scrollHeight - body.clientHeight;
|
||||||
|
const scrollRatio = maxScrollTop / (wrapperHeight - scrollbarHeight);
|
||||||
|
|
||||||
|
let newTop = parseFloat(verticalScrollbar.style.top || "0") + deltaY;
|
||||||
|
newTop = Math.max(0, Math.min(newTop, wrapperHeight - scrollbarHeight));
|
||||||
|
|
||||||
|
verticalScrollbar.style.top = `${newTop}px`;
|
||||||
|
body.scrollTop = newTop * scrollRatio;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateHorizontalScrollbar: (deltaX, deltaY) => {
|
||||||
|
const wrapperWidth = horizontalWrapper.offsetWidth;
|
||||||
|
const scrollbarWidth = horizontalScrollbar.offsetWidth;
|
||||||
|
const maxScrollLeft = table.scrollWidth - table.clientWidth;
|
||||||
|
const scrollRatio = maxScrollLeft / (wrapperWidth - scrollbarWidth);
|
||||||
|
|
||||||
|
let newLeft = parseFloat(horizontalScrollbar.style.left || "0") + deltaX;
|
||||||
|
newLeft = Math.max(0, Math.min(newLeft, wrapperWidth - scrollbarWidth));
|
||||||
|
|
||||||
|
horizontalScrollbar.style.left = `${newLeft}px`;
|
||||||
|
table.scrollLeft = newLeft * scrollRatio;
|
||||||
|
},
|
||||||
|
|
||||||
|
verticalMouseDown: (e) => {
|
||||||
|
disableTooltip();
|
||||||
|
dragManager.isDragging = true;
|
||||||
|
dragManager.startY = e.clientY;
|
||||||
|
dragManager.startX = e.clientX;
|
||||||
|
document.body.style.userSelect = "none";
|
||||||
|
verticalScrollbar.classList.add("dt2-dragging");
|
||||||
|
},
|
||||||
|
|
||||||
|
horizontalMouseDown: (e) => {
|
||||||
|
disableTooltip();
|
||||||
|
dragManager.isDragging = true;
|
||||||
|
dragManager.startY = e.clientY;
|
||||||
|
dragManager.startX = e.clientX;
|
||||||
|
document.body.style.userSelect = "none";
|
||||||
|
horizontalScrollbar.classList.add("dt2-dragging");
|
||||||
|
},
|
||||||
|
|
||||||
|
mouseMove: (e) => {
|
||||||
|
if (dragManager.isDragging) {
|
||||||
|
const deltaY = e.clientY - dragManager.startY;
|
||||||
|
const deltaX = e.clientX - dragManager.startX;
|
||||||
|
|
||||||
|
// Determine which scrollbar is being dragged
|
||||||
|
if (verticalScrollbar.classList.contains("dt2-dragging")) {
|
||||||
|
dragManager.updateVerticalScrollbar(deltaX, deltaY);
|
||||||
|
} else if (horizontalScrollbar.classList.contains("dt2-dragging")) {
|
||||||
|
dragManager.updateHorizontalScrollbar(deltaX, deltaY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset start points for next update
|
||||||
|
dragManager.startY = e.clientY;
|
||||||
|
dragManager.startX = e.clientX;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mouseUp: () => {
|
||||||
|
dragManager.isDragging = false;
|
||||||
|
document.body.style.userSelect = "";
|
||||||
|
verticalScrollbar.classList.remove("dt2-dragging");
|
||||||
|
horizontalScrollbar.classList.remove("dt2-dragging");
|
||||||
|
enableTooltip();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const wheelManager = {
|
||||||
|
handleWheelScrolling: (event) => {
|
||||||
|
const deltaX = event.deltaX;
|
||||||
|
const deltaY = event.deltaY;
|
||||||
|
|
||||||
|
// Scroll the body and table content
|
||||||
|
body.scrollTop += deltaY; // Vertical scrolling
|
||||||
|
table.scrollLeft += deltaX; // Horizontal scrolling
|
||||||
|
|
||||||
|
// Update the vertical scrollbar position
|
||||||
|
updateVerticalScrollbarForMouseWheel();
|
||||||
|
|
||||||
|
// Prevent default behavior to fully manage the scroll
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeManager = {
|
||||||
|
handleResize: () => {
|
||||||
|
computeScrollbarVisibility();
|
||||||
|
computeScrollbarSize();
|
||||||
|
updateVerticalScrollbarForMouseWheel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store managers on datagrid for cleanup
|
||||||
|
datagrid._managers = {
|
||||||
|
dragManager,
|
||||||
|
wheelManager,
|
||||||
|
resizeManager
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bind events
|
||||||
|
verticalScrollbar.addEventListener("mousedown", dragManager.verticalMouseDown);
|
||||||
|
horizontalScrollbar.addEventListener("mousedown", dragManager.horizontalMouseDown);
|
||||||
|
document.addEventListener("mousemove", dragManager.mouseMove);
|
||||||
|
document.addEventListener("mouseup", dragManager.mouseUp);
|
||||||
|
|
||||||
|
body.addEventListener("wheel", wheelManager.handleWheelScrolling, {passive: false});
|
||||||
|
|
||||||
|
window.addEventListener("resize", resizeManager.handleResize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always execute computations
|
||||||
|
computeScrollbarVisibility();
|
||||||
|
computeScrollbarSize();
|
||||||
|
}
|
||||||
|
|
||||||
function makeResizable(datagridId) {
|
function makeResizable(datagridId) {
|
||||||
console.debug("makeResizable on element " + datagridId);
|
console.debug("makeResizable on element " + datagridId);
|
||||||
|
|
||||||
|
|||||||
@@ -446,8 +446,8 @@ class DataGrid(BaseComponent):
|
|||||||
_mk_keyboard_management(),
|
_mk_keyboard_management(),
|
||||||
Div(
|
Div(
|
||||||
self.mk_table_header(),
|
self.mk_table_header(),
|
||||||
# self.mk_table_body(),
|
#self.mk_table_body(),
|
||||||
# self.mk_table_body_sse(),
|
#self.mk_table_body_sse(),
|
||||||
self.mk_table_body_page(),
|
self.mk_table_body_page(),
|
||||||
self.mk_table_footer(),
|
self.mk_table_footer(),
|
||||||
cls="dt2-inner-table"),
|
cls="dt2-inner-table"),
|
||||||
@@ -490,7 +490,12 @@ class DataGrid(BaseComponent):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def mk_table_body_sse(self):
|
def mk_table_body_sse(self):
|
||||||
|
"""
|
||||||
|
This function is used to create a sse update
|
||||||
|
Unfortunately, the sse does not update the correct element
|
||||||
|
tb_{self._id} is not updated
|
||||||
|
Plus UI refreshment issues
|
||||||
|
"""
|
||||||
max_height = self._compute_body_max_height()
|
max_height = self._compute_body_max_height()
|
||||||
|
|
||||||
return Div(
|
return Div(
|
||||||
@@ -505,7 +510,10 @@ class DataGrid(BaseComponent):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def mk_table_body_page(self):
|
def mk_table_body_page(self):
|
||||||
|
"""
|
||||||
|
This function is used to update the table body when the vertical scrollbar reaches the end
|
||||||
|
A new page is added when requested
|
||||||
|
"""
|
||||||
max_height = self._compute_body_max_height()
|
max_height = self._compute_body_max_height()
|
||||||
|
|
||||||
return Div(
|
return Div(
|
||||||
@@ -552,13 +560,17 @@ class DataGrid(BaseComponent):
|
|||||||
else:
|
else:
|
||||||
last_row = None
|
last_row = None
|
||||||
|
|
||||||
return [Div(
|
rows = [Div(
|
||||||
*[self.mk_body_cell(col_pos, row_index, col_def) for col_pos, col_def in enumerate(self._state.columns)],
|
*[self.mk_body_cell(col_pos, row_index, col_def) for col_pos, col_def in enumerate(self._state.columns)],
|
||||||
cls="dt2-row",
|
cls="dt2-row",
|
||||||
data_row=f"{row_index}",
|
data_row=f"{row_index}",
|
||||||
id=f"tr_{self._id}-{row_index}",
|
id=f"tr_{self._id}-{row_index}",
|
||||||
**self.commands.get_page(page_index + 1) if row_index == last_row else {}
|
**self.commands.get_page(page_index + 1) if row_index == last_row else {}
|
||||||
) for row_index in df.index[start:end]]
|
) for row_index in df.index[start:end]]
|
||||||
|
|
||||||
|
rows.append(Script(f"manageScrollbars('{self._id}', false);"),)
|
||||||
|
|
||||||
|
return rows
|
||||||
|
|
||||||
async def mk_body_content_sse(self):
|
async def mk_body_content_sse(self):
|
||||||
df = self._get_filtered_df()
|
df = self._get_filtered_df()
|
||||||
|
|||||||
@@ -100,9 +100,10 @@ class DataGridCommandManager(BaseCommandManager):
|
|||||||
def get_page(self, page_index=0):
|
def get_page(self, page_index=0):
|
||||||
return {
|
return {
|
||||||
"hx-get": f"{ROUTE_ROOT}{Routes.GetPage}",
|
"hx-get": f"{ROUTE_ROOT}{Routes.GetPage}",
|
||||||
"hx-swap": "afterend",
|
"hx-target": f"#tb_{self._id}",
|
||||||
"hx-vals": f'{{"_id": "{self._id}", "page": "{page_index}"}}',
|
"hx-swap": "beforeend",
|
||||||
"hx-trigger": "intersect once",
|
"hx-vals": f'{{"_id": "{self._id}", "page_index": "{page_index}"}}',
|
||||||
|
"hx-trigger": f"intersect root:#tb_{self._id} once",
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_hide_show_columns_attrs(self, mode, col_defs: list, new_value, cls=""):
|
def _get_hide_show_columns_attrs(self, mode, col_defs: list, new_value, cls=""):
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ attr_map = {
|
|||||||
"_id": "id",
|
"_id": "id",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def safe_attr(attr_name):
|
||||||
|
attr_name = attr_name.replace("hx_", "hx-")
|
||||||
|
return attr_map.get(attr_name, attr_name)
|
||||||
|
|
||||||
def to_html(item):
|
def to_html(item):
|
||||||
if item is None:
|
if item is None:
|
||||||
@@ -31,7 +34,7 @@ class MyFt:
|
|||||||
|
|
||||||
def to_html(self):
|
def to_html(self):
|
||||||
body_items = [to_html(item) for item in self.children]
|
body_items = [to_html(item) for item in self.children]
|
||||||
return f"<{self.name} {' '.join(f'{attr_map.get(k, k)}="{v}"' for k, v in self.attrs.items())}>{' '.join(body_items)}</div>"
|
return f"<{self.name} {' '.join(f'{safe_attr(k)}="{v}"' for k, v in self.attrs.items())}>{' '.join(body_items)}</div>"
|
||||||
|
|
||||||
def __ft__(self):
|
def __ft__(self):
|
||||||
return NotStr(self.to_html())
|
return NotStr(self.to_html())
|
||||||
|
|||||||
Reference in New Issue
Block a user