Implemented lazy loading

This commit is contained in:
2026-01-11 15:49:20 +01:00
parent a9eb23ad76
commit 5d6c02001e
8 changed files with 151 additions and 116 deletions

View File

@@ -11,6 +11,7 @@ from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.datagrid_objects import DataGridColumnState, DataGridRowState, \
DatagridSelectionState, DataGridHeaderFooterConf, DatagridEditionState
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.core.constants import ColumnType, ROW_INDEX_ID, FooterAggregation, DATAGRID_PAGE_SIZE, FILTER_INPUT_CID
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import MultipleInstance
@@ -25,14 +26,14 @@ _HTML_SPECIAL_CHARS_REGEX = re.compile(r'[<>&"\']')
@lru_cache(maxsize=2)
def _mk_bool_cached(_value):
"""
OPTIMIZED: Cached boolean checkbox HTML generator.
Since there are only 2 possible values (True/False), this will only generate HTML twice.
"""
return NotStr(str(
Div(mk.icon(checkbox_checked16_regular if _value else checkbox_unchecked16_regular, can_select=False),
cls="dt2-cell-content-checkbox")
))
"""
OPTIMIZED: Cached boolean checkbox HTML generator.
Since there are only 2 possible values (True/False), this will only generate HTML twice.
"""
return NotStr(str(
Div(mk.icon(checkbox_checked16_regular if _value else checkbox_unchecked16_regular, can_select=False),
cls="dt2-cell-content-checkbox")
))
class DatagridState(DbObject):
@@ -41,7 +42,7 @@ class DatagridState(DbObject):
with self.initializing():
self.sidebar_visible: bool = False
self.selected_view: str = None
self.row_index: bool = False
self.row_index: bool = True
self.columns: list[DataGridColumnState] = []
self.rows: list[DataGridRowState] = [] # only the rows that have a specific state
self.headers: list[DataGridHeaderFooterConf] = []
@@ -70,7 +71,17 @@ class DatagridSettings(DbObject):
class Commands(BaseCommands):
pass
def get_page(self, page_index: int):
return Command("GetPage",
"Get a specific page of data",
self._owner,
self._owner.mk_body_content_page,
kwargs={"page_index": page_index}
).htmx(target=f"#tb_{self._id}",
swap="beforeend",
trigger=f"intersect root:#tb_{self._id} once",
auto_swap_oob=False
)
class DataGrid(MultipleInstance):
@@ -175,18 +186,18 @@ class DataGrid(MultipleInstance):
- Avoids html.escape when not necessary
- Uses cached boolean HTML (_mk_bool_cached)
"""
def mk_highlighted_text(value_str, css_class):
"""Return highlighted text as raw HTML string or tuple of Spans."""
if not filter_keyword_lower:
# OPTIMIZATION: Return plain HTML string instead of Label object
# Include "truncate text-sm" to match mk.label() behavior (ellipsis + font size)
return NotStr(f'<span class="{css_class} truncate text-sm">{value_str}</span>')
index = value_str.lower().find(filter_keyword_lower)
if index < 0:
return NotStr(f'<span class="{css_class} truncate text-sm">{value_str}</span>')
# Has highlighting - need to use Span objects
# Add "truncate text-sm" to match mk.label() behavior
len_keyword = len(filter_keyword_lower)
@@ -197,25 +208,25 @@ class DataGrid(MultipleInstance):
if index + len_keyword < len(value_str):
res.append(Span(value_str[index + len_keyword:], cls=f"{css_class} text-sm"))
return Span(*res, cls=f"{css_class} truncate") if len(res) > 1 else res[0]
column_type = col_def.type
value = self._state.ns_fast_access[col_def.col_id][row_index]
# Boolean type - uses cached HTML (only 2 possible values)
if column_type == ColumnType.Bool:
return _mk_bool_cached(value)
# RowIndex - simplest case, just return the number as plain HTML
if column_type == ColumnType.RowIndex:
return NotStr(f'<span class="dt2-cell-content-number truncate text-sm">{row_index}</span>')
# Convert value to string
value_str = str(value)
# OPTIMIZATION: Only escape if necessary (check for HTML special chars with pre-compiled regex)
if _HTML_SPECIAL_CHARS_REGEX.search(value_str):
value_str = html.escape(value_str)
# Number or Text type
if column_type == ColumnType.Number:
return mk_highlighted_text(value_str, "dt2-cell-content-number")
@@ -229,12 +240,12 @@ class DataGrid(MultipleInstance):
"""
if not col_def.usable:
return None
if not col_def.visible:
return OptimizedDiv(cls="dt2-col-hidden")
content = self.mk_body_cell_content(col_pos, row_index, col_def, filter_keyword_lower)
return OptimizedDiv(content,
data_col=col_def.col_id,
style=f"width:{col_def.width}px;",
@@ -252,19 +263,19 @@ class DataGrid(MultipleInstance):
last_row = df.index[end - 1]
else:
last_row = None
# OPTIMIZATION: Extract filter keyword once (was being checked 10,000 times)
filter_keyword = self._state.filtered.get(FILTER_INPUT_CID)
filter_keyword_lower = filter_keyword.lower() if filter_keyword else None
rows = [OptimizedDiv(
*[self.mk_body_cell(col_pos, row_index, col_def, filter_keyword_lower)
for col_pos, col_def in enumerate(self._state.columns)],
cls="dt2-row",
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).get_htmx_params(escaped=True) if row_index == last_row else {}
) for row_index in df.index[start:end]]
return rows
def mk_body(self):
@@ -290,7 +301,9 @@ class DataGrid(MultipleInstance):
return Div(
self.mk_headers(),
self.mk_body(),
self.mk_footers()
self.mk_footers(),
cls="dt2-table",
id=f"t_{self._id}",
)
def mk_aggregation_cell(self, col_def, row_index: int, footer_conf, oob=False):

View File

@@ -1,5 +1,6 @@
import uuid
from dataclasses import dataclass
from io import BytesIO
import pandas as pd
from fasthtml.components import Div
@@ -90,7 +91,7 @@ class DataGridsManager(MultipleInstance):
def open_from_excel(self, tab_id, file_upload: FileUpload):
excel_content = file_upload.get_content()
df = pd.read_excel(excel_content, file_upload.get_sheet_name())
df = pd.read_excel(BytesIO(excel_content), file_upload.get_sheet_name())
dg = DataGrid(self._tabs_manager, save_state=True)
dg.init_from_dataframe(df)
document = DocumentDefinition(

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass, field
from myfasthtml.core.constants import ColumnType, DEFAULT_COLUMN_WIDTH, ViewType
from myfasthtml.core.constants import ColumnType, DATAGRID_DEFAULT_COLUMN_WIDTH, ViewType
@dataclass
@@ -17,7 +17,7 @@ class DataGridColumnState:
type: ColumnType = ColumnType.Text
visible: bool = True
usable: bool = True
width: int = DEFAULT_COLUMN_WIDTH
width: int = DATAGRID_DEFAULT_COLUMN_WIDTH
@dataclass