I can hide and show the columns, and it dynamically updates the grid
This commit is contained in:
@@ -1161,3 +1161,19 @@
|
||||
.dt2-moving {
|
||||
transition: transform 300ms ease;
|
||||
}
|
||||
|
||||
/* *********************************************** */
|
||||
/* ******** DataGrid Column Manager ********** */
|
||||
/* *********************************************** */
|
||||
.dt2-column-manager-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border-radius: 0.375rem;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.dt2-column-manager-label:hover {
|
||||
background-color: var(--color-base-300);
|
||||
}
|
||||
|
||||
@@ -1200,8 +1200,10 @@ function triggerHtmxAction(elementId, config, combinationStr, isInside, event) {
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent default if we found any match and not in input context
|
||||
if (currentMatches.length > 0 && !isInInputContext()) {
|
||||
// Prevent default only if click was INSIDE a registered element
|
||||
// Clicks outside should preserve native behavior (checkboxes, buttons, etc.)
|
||||
const anyMatchInside = currentMatches.some(match => match.isInside);
|
||||
if (currentMatches.length > 0 && anyMatchInside && !isInInputContext()) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ from myfasthtml.controls.Mouse import Mouse
|
||||
from myfasthtml.controls.Panel import Panel, PanelConf
|
||||
from myfasthtml.controls.datagrid_objects import DataGridColumnState, DataGridRowState, \
|
||||
DatagridSelectionState, DataGridHeaderFooterConf, DatagridEditionState
|
||||
from myfasthtml.controls.helpers import mk
|
||||
from myfasthtml.controls.helpers import mk, icons
|
||||
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
|
||||
@@ -138,6 +138,13 @@ class Commands(BaseCommands):
|
||||
self._owner.toggle_columns_manager
|
||||
).htmx(target=None)
|
||||
|
||||
def on_column_changed(self):
|
||||
return Command("OnColumnChanged",
|
||||
"Column definition changed",
|
||||
self._owner,
|
||||
self._owner.on_column_changed
|
||||
)
|
||||
|
||||
|
||||
class DataGrid(MultipleInstance):
|
||||
def __init__(self, parent, settings=None, save_state=None, _id=None):
|
||||
@@ -170,6 +177,7 @@ class DataGrid(MultipleInstance):
|
||||
|
||||
# add columns manager
|
||||
self._columns_manager = DataGridColumnsManager(self)
|
||||
self._columns_manager.bind_command("ToggleColumn", self.commands.on_column_changed())
|
||||
|
||||
# other definitions
|
||||
self._mouse_support = {
|
||||
@@ -354,7 +362,7 @@ class DataGrid(MultipleInstance):
|
||||
def filter(self):
|
||||
logger.debug("filter")
|
||||
self._state.filtered[FILTER_INPUT_CID] = self._datagrid_filter.get_query()
|
||||
return self.render_partial("body", redraw_scrollbars=True)
|
||||
return self.render_partial("body")
|
||||
|
||||
def on_click(self, combination, is_inside, cell_id):
|
||||
logger.debug(f"on_click {combination=} {is_inside=} {cell_id=}")
|
||||
@@ -365,6 +373,10 @@ class DataGrid(MultipleInstance):
|
||||
|
||||
return self.render_partial()
|
||||
|
||||
def on_column_changed(self):
|
||||
logger.debug("on_column_changed")
|
||||
return self.render_partial("table")
|
||||
|
||||
def change_selection_mode(self):
|
||||
logger.debug(f"change_selection_mode")
|
||||
new_state = self._selection_mode_selector.get_state()
|
||||
@@ -377,17 +389,27 @@ class DataGrid(MultipleInstance):
|
||||
logger.debug(f"toggle_columns_manager")
|
||||
self._panel.set_right(self._columns_manager)
|
||||
|
||||
def save_state(self):
|
||||
self._state.save()
|
||||
|
||||
def get_state(self):
|
||||
return self._state
|
||||
|
||||
def mk_headers(self):
|
||||
resize_cmd = self.commands.set_column_width()
|
||||
move_cmd = self.commands.move_column()
|
||||
|
||||
def _mk_header_name(col_def: DataGridColumnState):
|
||||
return Div(
|
||||
mk.label(col_def.title, name="dt2-header-title"),
|
||||
mk.label(col_def.title, icon=icons.get(col_def.type, None)),
|
||||
# make room for sort and filter indicators
|
||||
cls="flex truncate cursor-default",
|
||||
)
|
||||
|
||||
def _mk_header(col_def: DataGridColumnState):
|
||||
if not col_def.visible:
|
||||
return None
|
||||
|
||||
return Div(
|
||||
_mk_header_name(col_def),
|
||||
Div(cls="dt2-resize-handle", data_command_id=resize_cmd.id),
|
||||
@@ -397,7 +419,7 @@ class DataGrid(MultipleInstance):
|
||||
cls="dt2-cell dt2-resizable flex",
|
||||
)
|
||||
|
||||
header_class = "dt2-row dt2-header" + "" if self._settings.header_visible else " hidden"
|
||||
header_class = "dt2-row dt2-header"
|
||||
return Div(
|
||||
*[_mk_header(col_def) for col_def in self._state.columns],
|
||||
cls=header_class,
|
||||
@@ -470,7 +492,7 @@ class DataGrid(MultipleInstance):
|
||||
return None
|
||||
|
||||
if not col_def.visible:
|
||||
return OptimizedDiv(cls="dt2-col-hidden")
|
||||
return None
|
||||
|
||||
value = self._state.ns_fast_access[col_def.col_id][row_index]
|
||||
content = self.mk_body_cell_content(col_pos, row_index, col_def, filter_keyword_lower)
|
||||
@@ -509,7 +531,7 @@ class DataGrid(MultipleInstance):
|
||||
|
||||
return rows
|
||||
|
||||
def mk_body_container(self):
|
||||
def mk_body_wrapper(self):
|
||||
return Div(
|
||||
self.mk_body(),
|
||||
cls="dt2-body-container",
|
||||
@@ -535,28 +557,12 @@ class DataGrid(MultipleInstance):
|
||||
id=f"tf_{self._id}"
|
||||
)
|
||||
|
||||
def mk_table(self):
|
||||
def mk_table_wrapper(self):
|
||||
return Div(
|
||||
self.mk_selection_manager(),
|
||||
|
||||
# Grid table with header, body, footer
|
||||
Div(
|
||||
# Header container - no scroll
|
||||
Div(
|
||||
self.mk_headers(),
|
||||
cls="dt2-header-container"
|
||||
),
|
||||
self.mk_table(),
|
||||
|
||||
self.mk_body_container(), # Body container - scroll via JS, scrollbars hidden
|
||||
|
||||
# Footer container - no scroll
|
||||
Div(
|
||||
self.mk_footers(),
|
||||
cls="dt2-footer-container"
|
||||
),
|
||||
cls="dt2-table",
|
||||
id=f"t_{self._id}"
|
||||
),
|
||||
# Custom scrollbars overlaid
|
||||
Div(
|
||||
# Vertical scrollbar wrapper (right side)
|
||||
@@ -575,6 +581,26 @@ class DataGrid(MultipleInstance):
|
||||
id=f"tw_{self._id}"
|
||||
)
|
||||
|
||||
def mk_table(self):
|
||||
# Grid table with header, body, footer
|
||||
return Div(
|
||||
# Header container - no scroll
|
||||
Div(
|
||||
self.mk_headers(),
|
||||
cls="dt2-header-container"
|
||||
),
|
||||
|
||||
self.mk_body_wrapper(), # Body container - scroll via JS, scrollbars hidden
|
||||
|
||||
# Footer container - no scroll
|
||||
Div(
|
||||
self.mk_footers(),
|
||||
cls="dt2-footer-container"
|
||||
),
|
||||
cls="dt2-table",
|
||||
id=f"t_{self._id}"
|
||||
)
|
||||
|
||||
def mk_selection_manager(self):
|
||||
|
||||
extra_attr = {
|
||||
@@ -667,7 +693,7 @@ class DataGrid(MultipleInstance):
|
||||
mk.icon(settings16_regular, command=self.commands.toggle_columns_manager(), tooltip="Show sidebar"),
|
||||
cls="flex"),
|
||||
cls="flex items-center justify-between mb-2"),
|
||||
self._panel.set_main(self.mk_table()),
|
||||
self._panel.set_main(self.mk_table_wrapper()),
|
||||
Script(f"initDataGrid('{self._id}');"),
|
||||
Mouse(self, combinations=self._mouse_support),
|
||||
id=self._id,
|
||||
@@ -675,7 +701,7 @@ class DataGrid(MultipleInstance):
|
||||
style="height: 100%; grid-template-rows: auto 1fr;"
|
||||
)
|
||||
|
||||
def render_partial(self, fragment="cell", redraw_scrollbars=False):
|
||||
def render_partial(self, fragment="cell"):
|
||||
"""
|
||||
|
||||
:param fragment: cell | body
|
||||
@@ -689,10 +715,15 @@ class DataGrid(MultipleInstance):
|
||||
}
|
||||
|
||||
if fragment == "body":
|
||||
body_container = self.mk_body_container()
|
||||
body_container = self.mk_body_wrapper()
|
||||
body_container.attrs.update(extra_attr)
|
||||
res.append(body_container)
|
||||
|
||||
elif fragment == "table":
|
||||
table = self.mk_table()
|
||||
table.attrs.update(extra_attr)
|
||||
res.append(table)
|
||||
|
||||
res.append(self.mk_selection_manager())
|
||||
|
||||
return tuple(res)
|
||||
|
||||
@@ -1,23 +1,61 @@
|
||||
import logging
|
||||
|
||||
from fasthtml.components import *
|
||||
|
||||
from myfasthtml.controls.BaseCommands import BaseCommands
|
||||
from myfasthtml.controls.Search import Search
|
||||
from myfasthtml.controls.datagrid_objects import DataGridColumnState
|
||||
from myfasthtml.controls.helpers import icons, mk
|
||||
from myfasthtml.core.commands import Command
|
||||
from myfasthtml.core.instances import MultipleInstance
|
||||
from myfasthtml.icons.fluent_p1 import chevron_right20_regular
|
||||
|
||||
logger = logging.getLogger("DataGridColumnsManager")
|
||||
|
||||
|
||||
class Commands(BaseCommands):
|
||||
def toggle_column(self, col_id):
|
||||
return Command(f"ToggleColumn",
|
||||
f"Toggle column {col_id}",
|
||||
self._owner,
|
||||
self._owner.toggle_column,
|
||||
kwargs={"col_id": col_id}).htmx(swap="outerHTML", target=f"#tcolman_{self._id}-{col_id}")
|
||||
|
||||
|
||||
class DataGridColumnsManager(MultipleInstance):
|
||||
def __init__(self, parent, _id=None):
|
||||
super().__init__(parent, _id=_id)
|
||||
self.commands = Commands(self)
|
||||
|
||||
@property
|
||||
def columns(self):
|
||||
return self._parent._state.columns
|
||||
return self._parent.get_state().columns
|
||||
|
||||
def toggle_column(self, col_id):
|
||||
logger.debug(f"toggle_column {col_id=}")
|
||||
cols_defs = [c for c in self.columns if c.col_id == col_id]
|
||||
if not cols_defs:
|
||||
logger.debug(f" column '{col_id}' is not found.")
|
||||
return Div(f"Column '{col_id}' not found")
|
||||
|
||||
col_def = cols_defs[0]
|
||||
col_def.visible = not col_def.visible
|
||||
self._parent.save_state()
|
||||
return self.mk_column(col_def)
|
||||
|
||||
def mk_column(self, col_def: DataGridColumnState):
|
||||
return Div(
|
||||
Input(type="checkbox", checked=col_def.visible, cls="ml-2"),
|
||||
Label(col_def.col_id, cls="ml-2"),
|
||||
cls="flex mb-1",
|
||||
mk.mk(
|
||||
Input(type="checkbox", cls="checkbox checkbox-sm", checked=col_def.visible),
|
||||
command=self.commands.toggle_column(col_def.col_id)
|
||||
),
|
||||
Div(
|
||||
Div(mk.label(col_def.col_id, icon=icons.get(col_def.type, None), cls="ml-2")),
|
||||
Div(mk.icon(chevron_right20_regular), cls="mr-2"),
|
||||
cls="dt2-column-manager-label"
|
||||
),
|
||||
cls="flex mb-1 items-center",
|
||||
id=f"tcolman_{self._id}-{col_def.col_id}"
|
||||
)
|
||||
|
||||
def render(self):
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
from fastcore.xml import FT
|
||||
from fasthtml.components import *
|
||||
|
||||
from myfasthtml.core.bindings import Binding
|
||||
from myfasthtml.core.commands import Command, CommandTemplate
|
||||
from myfasthtml.core.constants import ColumnType
|
||||
from myfasthtml.core.utils import merge_classes
|
||||
from myfasthtml.icons.fluent import question20_regular, brain_circuit20_regular, number_row20_regular, \
|
||||
number_symbol20_regular
|
||||
from myfasthtml.icons.fluent_p1 import checkbox_checked20_regular, checkbox_unchecked20_regular, \
|
||||
checkbox_checked20_filled
|
||||
from myfasthtml.icons.fluent_p2 import text_bullet_list_square20_regular, text_field20_regular
|
||||
from myfasthtml.icons.fluent_p3 import calendar_ltr20_regular
|
||||
|
||||
|
||||
class Ids:
|
||||
@@ -96,7 +104,7 @@ class mk:
|
||||
command: Command | CommandTemplate = None,
|
||||
binding: Binding = None,
|
||||
**kwargs):
|
||||
merged_cls = merge_classes("flex truncate", cls, kwargs)
|
||||
merged_cls = merge_classes("flex truncate items-center", cls, kwargs)
|
||||
icon_part = Span(icon, cls=f"mf-icon-{mk.convert_size(size)} mr-1") if icon else None
|
||||
text_part = Span(text, cls=f"text-{size}")
|
||||
return mk.mk(Label(icon_part, text_part, cls=merged_cls, **kwargs), command=command, binding=binding)
|
||||
@@ -138,3 +146,19 @@ class mk:
|
||||
ft = mk.manage_command(ft, command) if command else ft
|
||||
ft = mk.manage_binding(ft, binding, init_binding=init_binding) if binding else ft
|
||||
return ft
|
||||
|
||||
|
||||
icons = {
|
||||
None: question20_regular,
|
||||
True: checkbox_checked20_regular,
|
||||
False: checkbox_unchecked20_regular,
|
||||
|
||||
"Brain": brain_circuit20_regular,
|
||||
|
||||
ColumnType.RowIndex: number_symbol20_regular,
|
||||
ColumnType.Text: text_field20_regular,
|
||||
ColumnType.Number: number_row20_regular,
|
||||
ColumnType.Datetime: calendar_ltr20_regular,
|
||||
ColumnType.Bool: checkbox_checked20_filled,
|
||||
ColumnType.List: text_bullet_list_square20_regular,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user