Implemented lazy loading
This commit is contained in:
@@ -965,7 +965,7 @@ input:focus {
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
/* overflow: hidden; */
|
||||
}
|
||||
|
||||
.dt2-table:focus {
|
||||
|
||||
@@ -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
|
||||
@@ -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):
|
||||
@@ -253,7 +264,6 @@ class DataGrid(MultipleInstance):
|
||||
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
|
||||
|
||||
@@ -262,7 +272,8 @@ class DataGrid(MultipleInstance):
|
||||
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
|
||||
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import html
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
@@ -11,6 +12,7 @@ from myfasthtml.core.utils import flatten
|
||||
|
||||
logger = logging.getLogger("Commands")
|
||||
|
||||
AUTO_SWAP_OOB = "__auto_swap_oob__"
|
||||
|
||||
class Command:
|
||||
"""
|
||||
@@ -71,7 +73,7 @@ class Command:
|
||||
self.callback = callback
|
||||
self.default_args = args or []
|
||||
self.default_kwargs = kwargs or {}
|
||||
self._htmx_extra = {}
|
||||
self._htmx_extra = {AUTO_SWAP_OOB: True}
|
||||
self._bindings = []
|
||||
self._ft = None
|
||||
self._callback_parameters = dict(inspect.signature(callback).parameters) if callback else {}
|
||||
@@ -97,7 +99,7 @@ class Command:
|
||||
def get_key(self):
|
||||
return self._key
|
||||
|
||||
def get_htmx_params(self):
|
||||
def get_htmx_params(self, escaped=False):
|
||||
res = {
|
||||
"hx-post": f"{ROUTE_ROOT}{Routes.Commands}",
|
||||
"hx-swap": "outerHTML",
|
||||
@@ -115,10 +117,13 @@ class Command:
|
||||
# kwarg are given to the callback as values
|
||||
res["hx-vals"] |= self.default_kwargs
|
||||
|
||||
if escaped:
|
||||
res["hx-vals"] = html.escape(json.dumps(res["hx-vals"]))
|
||||
|
||||
return res
|
||||
|
||||
def execute(self, client_response: dict = None):
|
||||
logger.debug(f"Executing command {self.name}")
|
||||
logger.debug(f"Executing command {self.name} with arguments {client_response=}")
|
||||
with ObservableResultCollector(self._bindings) as collector:
|
||||
kwargs = self._create_kwargs(self.default_kwargs,
|
||||
client_response,
|
||||
@@ -135,6 +140,7 @@ class Command:
|
||||
all_ret = flatten(ret, ret_from_bound_commands, collector.results)
|
||||
|
||||
# Set the hx-swap-oob attribute on all elements returned by the callback
|
||||
if self._htmx_extra[AUTO_SWAP_OOB]:
|
||||
for r in all_ret[1:]:
|
||||
if (hasattr(r, 'attrs')
|
||||
and "hx-swap-oob" not in r.attrs
|
||||
@@ -143,7 +149,9 @@ class Command:
|
||||
|
||||
return all_ret[0] if len(all_ret) == 1 else all_ret
|
||||
|
||||
def htmx(self, target: Optional[str] = "this", swap="outerHTML", trigger=None):
|
||||
def htmx(self, target: Optional[str] = "this", swap="outerHTML", trigger=None, auto_swap_oob=True):
|
||||
self._htmx_extra[AUTO_SWAP_OOB] = auto_swap_oob
|
||||
|
||||
# Note that the default value is the same than in get_htmx_params()
|
||||
if target is None:
|
||||
self._htmx_extra["hx-swap"] = "none"
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
from enum import Enum
|
||||
|
||||
DEFAULT_COLUMN_WIDTH = 100
|
||||
NO_DEFAULT_VALUE = object()
|
||||
|
||||
ROUTE_ROOT = "/myfasthtml"
|
||||
|
||||
# Datagrid
|
||||
ROW_INDEX_ID = "__row_index__"
|
||||
DATAGRID_DEFAULT_COLUMN_WIDTH = 100
|
||||
DATAGRID_PAGE_SIZE = 1000
|
||||
FILTER_INPUT_CID = "__filter_input__"
|
||||
|
||||
|
||||
class Routes:
|
||||
Commands = "/commands"
|
||||
Bindings = "/bindings"
|
||||
|
||||
@@ -3,6 +3,7 @@ import uuid
|
||||
from typing import Optional
|
||||
|
||||
from myfasthtml.controls.helpers import Ids
|
||||
from myfasthtml.core.constants import NO_DEFAULT_VALUE
|
||||
from myfasthtml.core.utils import pascal_to_snake, get_class, snake_to_pascal
|
||||
|
||||
logger = logging.getLogger("InstancesManager")
|
||||
@@ -183,7 +184,7 @@ class InstancesManager:
|
||||
return instance
|
||||
|
||||
@staticmethod
|
||||
def get(session: dict, instance_id: str, default="**__no_default__**"):
|
||||
def get(session: dict, instance_id: str, default=NO_DEFAULT_VALUE):
|
||||
"""
|
||||
Get or create an instance of the given type (from its id)
|
||||
:param session:
|
||||
@@ -196,9 +197,9 @@ class InstancesManager:
|
||||
key = (session_id, instance_id)
|
||||
return InstancesManager.instances[key]
|
||||
except KeyError:
|
||||
if default != "**__non__**":
|
||||
return default
|
||||
if default is NO_DEFAULT_VALUE:
|
||||
raise
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def get_by_type(session: dict, cls: type):
|
||||
|
||||
@@ -9,6 +9,8 @@ from functools import lru_cache
|
||||
|
||||
from fasthtml.common import NotStr
|
||||
|
||||
from myfasthtml.core.constants import NO_DEFAULT_VALUE
|
||||
|
||||
|
||||
class OptimizedFt:
|
||||
"""Lightweight FastHTML-compatible element that generates HTML directly."""
|
||||
@@ -74,6 +76,14 @@ class OptimizedFt:
|
||||
def __str__(self):
|
||||
return self.to_html()
|
||||
|
||||
def get(self, attr_name, default=NO_DEFAULT_VALUE):
|
||||
try:
|
||||
return self.attrs[self.safe_attr(attr_name)]
|
||||
except KeyError:
|
||||
if default is NO_DEFAULT_VALUE:
|
||||
raise
|
||||
return default
|
||||
|
||||
|
||||
class OptimizedDiv(OptimizedFt):
|
||||
"""Optimized Div element."""
|
||||
|
||||
Reference in New Issue
Block a user