I can edit a cell
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import html
|
||||
import logging
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from functools import lru_cache
|
||||
from typing import Optional
|
||||
|
||||
@@ -18,9 +17,7 @@ from myfasthtml.controls.Keyboard import Keyboard
|
||||
from myfasthtml.controls.Mouse import Mouse
|
||||
from myfasthtml.controls.Panel import Panel, PanelConf
|
||||
from myfasthtml.controls.Query import Query, QUERY_FILTER
|
||||
from myfasthtml.controls.datagrid_objects import DataGridColumnState, DataGridRowUiState, \
|
||||
DatagridSelectionState, DataGridHeaderFooterConf, DatagridEditionState, DataGridColumnUiState, \
|
||||
DataGridRowSelectionColumnState
|
||||
from myfasthtml.controls.datagrid_objects import *
|
||||
from myfasthtml.controls.helpers import mk
|
||||
from myfasthtml.core.commands import Command
|
||||
from myfasthtml.core.constants import ColumnType, FooterAggregation, DATAGRID_PAGE_SIZE, FILTER_INPUT_CID
|
||||
@@ -156,7 +153,7 @@ class Commands(BaseCommands):
|
||||
return Command("OnClick",
|
||||
"Click on the table",
|
||||
self._owner,
|
||||
self._owner.on_click
|
||||
self._owner.handle_on_click
|
||||
).htmx(target=f"#tsm_{self._id}")
|
||||
|
||||
def on_key_pressed(self):
|
||||
@@ -212,6 +209,21 @@ class Commands(BaseCommands):
|
||||
self._owner,
|
||||
self._owner.on_column_changed
|
||||
)
|
||||
|
||||
def start_edition(self):
|
||||
return Command("StartEdition",
|
||||
"Enter cell edit mode",
|
||||
self._owner,
|
||||
self._owner.handle_start_edition
|
||||
).htmx(target=f"#tsm_{self._id}")
|
||||
|
||||
def save_edition(self):
|
||||
return Command("SaveEdition",
|
||||
"Save cell edition",
|
||||
self._owner,
|
||||
self._owner.handle_save_edition
|
||||
).htmx(target=f"#tsm_{self._id}",
|
||||
trigger="blur, keydown[key=='Enter']")
|
||||
|
||||
|
||||
class DataGrid(MultipleInstance):
|
||||
@@ -293,10 +305,12 @@ class DataGrid(MultipleInstance):
|
||||
"click": {"command": self.commands.on_click(), "hx_vals": "js:getCellId()"},
|
||||
"ctrl+click": {"command": self.commands.on_click(), "hx_vals": "js:getCellId()"},
|
||||
"shift+click": {"command": self.commands.on_click(), "hx_vals": "js:getCellId()"},
|
||||
"dblclick": {"command": self.commands.start_edition(), "hx_vals": "js:getCellId()"},
|
||||
}
|
||||
|
||||
self._key_support = {
|
||||
"esc": {"command": self.commands.on_key_pressed(), "require_inside": False},
|
||||
"enter": {"command": self.commands.on_key_pressed(), "require_inside": True},
|
||||
}
|
||||
|
||||
logger.debug(f"DataGrid '{self.get_table_name()}' with id='{self._id}' created.")
|
||||
@@ -451,6 +465,26 @@ class DataGrid(MultipleInstance):
|
||||
if self._settings.enable_edition:
|
||||
self._columns.insert(0, DataGridRowSelectionColumnState())
|
||||
|
||||
def _enter_edition(self, pos):
|
||||
col_pos, row_index = pos
|
||||
col_def = self._columns[col_pos]
|
||||
if col_def.type in (ColumnType.RowSelection_, ColumnType.RowIndex, ColumnType.Formula):
|
||||
return self.render_partial()
|
||||
self._state.edition.under_edition = pos
|
||||
self._state.save()
|
||||
return self.render_partial("cell", pos=pos)
|
||||
|
||||
def _convert_edition_value(self, value_str, col_type):
|
||||
if col_type == ColumnType.Number:
|
||||
try:
|
||||
return float(value_str) if '.' in value_str else int(value_str)
|
||||
except (ValueError, TypeError):
|
||||
return value_str
|
||||
elif col_type == ColumnType.Bool:
|
||||
return value_str.lower() in ('true', '1', 'yes')
|
||||
else:
|
||||
return value_str
|
||||
|
||||
def add_new_column(self, col_def: DataGridColumnState) -> None:
|
||||
"""Add a new column, delegating data mutation to DataService.
|
||||
|
||||
@@ -571,14 +605,20 @@ class DataGrid(MultipleInstance):
|
||||
self._state.filtered[FILTER_INPUT_CID] = self._datagrid_filter.get_query()
|
||||
return self.render_partial("body")
|
||||
|
||||
def on_click(self, combination, is_inside, cell_id):
|
||||
def handle_on_click(self, combination, is_inside, cell_id):
|
||||
logger.debug(f"on_click table={self.get_table_name()} {combination=} {is_inside=} {cell_id=}")
|
||||
if is_inside and cell_id:
|
||||
self._state.selection.extra_selected.clear()
|
||||
|
||||
if cell_id.startswith("tcell_"):
|
||||
pos = self._get_pos_from_element_id(cell_id)
|
||||
self._update_current_position(pos)
|
||||
pos = self._get_pos_from_element_id(cell_id)
|
||||
|
||||
if (self._settings.enable_edition and
|
||||
pos is not None and
|
||||
pos == self._state.selection.selected and
|
||||
self._state.edition.under_edition is None):
|
||||
return self._enter_edition(pos)
|
||||
|
||||
self._update_current_position(pos)
|
||||
|
||||
return self.render_partial()
|
||||
|
||||
@@ -605,6 +645,11 @@ class DataGrid(MultipleInstance):
|
||||
if combination == "esc":
|
||||
self._update_current_position(None)
|
||||
self._state.selection.extra_selected.clear()
|
||||
elif (combination == "enter" and
|
||||
self._settings.enable_edition and
|
||||
self._state.selection.selected and
|
||||
self._state.edition.under_edition is None):
|
||||
return self._enter_edition(self._state.selection.selected)
|
||||
|
||||
return self.render_partial()
|
||||
|
||||
@@ -674,6 +719,30 @@ class DataGrid(MultipleInstance):
|
||||
self._panel.set_title(side="right", title="Formatting")
|
||||
self._panel.set_right(self._formatting_editor)
|
||||
|
||||
def handle_start_edition(self, cell_id):
|
||||
logger.debug(f"handle_start_edition: {cell_id=}")
|
||||
if not self._settings.enable_edition:
|
||||
return self.render_partial()
|
||||
if self._state.edition.under_edition is not None:
|
||||
return self.render_partial()
|
||||
pos = self._get_pos_from_element_id(cell_id)
|
||||
if pos is None:
|
||||
return self.render_partial()
|
||||
self._update_current_position(pos)
|
||||
return self._enter_edition(pos)
|
||||
|
||||
def handle_save_edition(self, value):
|
||||
logger.debug(f"handle_save_edition: {value=}")
|
||||
if self._state.edition.under_edition is None:
|
||||
return self.render_partial()
|
||||
col_pos, row_index = self._state.edition.under_edition
|
||||
col_def = self._columns[col_pos]
|
||||
typed_value = self._convert_edition_value(value, col_def.type)
|
||||
self._data_service.set_data(col_def.col_id, row_index, typed_value)
|
||||
self._state.edition.under_edition = None
|
||||
self._state.save()
|
||||
return self.render_partial("cell", pos=(col_pos, row_index))
|
||||
|
||||
def handle_set_column_width(self, col_id: str, width: str):
|
||||
"""Update column width after resize. Called via Command from JS."""
|
||||
logger.debug(f"set_column_width: {col_id=} {width=}")
|
||||
@@ -724,6 +793,31 @@ class DataGrid(MultipleInstance):
|
||||
def get_data_service_id_from_data_grid_id(datagrid_id):
|
||||
return datagrid_id.replace(DataGrid.compute_prefix(), DataService.compute_prefix(), 1)
|
||||
|
||||
def _mk_edition_cell(self, col_pos, row_index, col_def: DataGridColumnState, is_last):
|
||||
col_array = self._fast_access.get(col_def.col_id)
|
||||
value = col_array[row_index] if col_array is not None and row_index < len(col_array) else None
|
||||
value_str = str(value) if not is_null(value) else ""
|
||||
|
||||
save_cmd = self.commands.save_edition()
|
||||
input_elem = mk.mk(
|
||||
Input(value=value_str, name="value", autofocus=True, cls="dt2-cell-input"),
|
||||
command=save_cmd
|
||||
)
|
||||
|
||||
return OptimizedDiv(
|
||||
input_elem,
|
||||
id=self._get_element_id_from_pos("cell", (col_pos, row_index)),
|
||||
cls=merge_classes("dt2-cell dt2-cell-edition", "dt2-last-cell" if is_last else None),
|
||||
style=f"width:{col_def.width}px;"
|
||||
)
|
||||
|
||||
def _mk_cell_oob(self, col_pos, row_index):
|
||||
col_def = self._columns[col_pos]
|
||||
filter_keyword = self._state.filtered.get(FILTER_INPUT_CID)
|
||||
filter_keyword_lower = filter_keyword.lower() if filter_keyword else None
|
||||
is_last = col_pos == len(self._columns) - 1
|
||||
return self.mk_body_cell(col_pos, row_index, col_def, filter_keyword_lower, is_last)
|
||||
|
||||
def mk_headers(self):
|
||||
resize_cmd = self.commands.set_column_width()
|
||||
move_cmd = self.commands.move_column()
|
||||
@@ -867,6 +961,10 @@ class DataGrid(MultipleInstance):
|
||||
if col_def.type == ColumnType.RowSelection_:
|
||||
return OptimizedDiv(cls="dt2-row-selection")
|
||||
|
||||
if (self._settings.enable_edition and
|
||||
self._state.edition.under_edition == (col_pos, row_index)):
|
||||
return self._mk_edition_cell(col_pos, row_index, col_def, is_last)
|
||||
|
||||
col_array = self._fast_access.get(col_def.col_id)
|
||||
value = col_array[row_index] if col_array is not None and row_index < len(col_array) else None
|
||||
content = self.mk_body_cell_content(col_pos, row_index, col_def, filter_keyword_lower)
|
||||
@@ -1105,7 +1203,7 @@ class DataGrid(MultipleInstance):
|
||||
:param kwargs: Additional parameters for specific fragments (col_id, optimal_width for header)
|
||||
:return:
|
||||
"""
|
||||
res = []
|
||||
res = [self.mk_selection_manager()]
|
||||
|
||||
extra_attr = {
|
||||
"hx-on::after-settle": f"initDataGrid('{self._id}');",
|
||||
@@ -1133,7 +1231,21 @@ class DataGrid(MultipleInstance):
|
||||
header.attrs.update(header_extra_attr)
|
||||
return header
|
||||
|
||||
res.append(self.mk_selection_manager())
|
||||
else:
|
||||
col_pos, row_index = None, None
|
||||
|
||||
if (cell_id := kwargs.get("cell_id")) is not None:
|
||||
col_pos, row_index = self._get_pos_from_element_id(cell_id)
|
||||
elif (pos := kwargs.get("pos")) is not None:
|
||||
col_pos, row_index = pos
|
||||
|
||||
if col_pos is not None and row_index is not None:
|
||||
col_def = self._columns[col_pos]
|
||||
filter_keyword = self._state.filtered.get(FILTER_INPUT_CID)
|
||||
filter_keyword_lower = filter_keyword.lower() if filter_keyword else None
|
||||
is_last_col = col_pos == len(self._columns) - 1
|
||||
cell = self.mk_body_cell(col_pos, row_index, col_def, filter_keyword_lower, is_last_col)
|
||||
res.append(cell)
|
||||
|
||||
return tuple(res)
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ class Mouse(MultipleInstance):
|
||||
|
||||
VALID_ACTIONS = {
|
||||
'click', 'right_click', 'rclick',
|
||||
'mousedown>mouseup', 'rmousedown>mouseup'
|
||||
'mousedown>mouseup', 'rmousedown>mouseup',
|
||||
'dblclick', 'double_click', 'dclick'
|
||||
}
|
||||
VALID_MODIFIERS = {'ctrl', 'shift', 'alt'}
|
||||
def __init__(self, parent, _id=None, combinations=None):
|
||||
|
||||
Reference in New Issue
Block a user