diff --git a/src/myfasthtml/controls/DataGrid.py b/src/myfasthtml/controls/DataGrid.py
index 7217df9..fe7c4c0 100644
--- a/src/myfasthtml/controls/DataGrid.py
+++ b/src/myfasthtml/controls/DataGrid.py
@@ -233,7 +233,7 @@ class DataGrid(MultipleInstance):
# add columns manager
self._columns_manager = DataGridColumnsManager(self)
self._columns_manager.bind_command("ToggleColumn", self.commands.on_column_changed())
- self._columns_manager.bind_command("UpdateColumn", self.commands.on_column_changed())
+ self._columns_manager.bind_command("SaveColumnDetails", self.commands.on_column_changed())
if self._settings.enable_formatting:
completion_engine = FormattingCompletionEngine(self._parent, self.get_table_name())
@@ -418,8 +418,7 @@ class DataGrid(MultipleInstance):
self._df_store.ns_fast_access = _init_fast_access(self._df)
self._df_store.ns_row_data = _init_row_data(self._df)
self._df_store.ns_total_rows = len(self._df) if self._df is not None else 0
- if init_state:
- self._register_existing_formulas()
+ self._register_existing_formulas()
return self
@@ -677,8 +676,27 @@ class DataGrid(MultipleInstance):
return Span(*res, cls=f"{css_class} truncate", style=style) if len(res) > 1 else res[0]
column_type = col_def.type
- value = self._df_store.ns_fast_access[col_def.col_id][row_index]
-
+
+ # Formula column: safe read — ns_fast_access entry may not exist yet if the
+ # engine hasn't run its first recalculation pass.
+ if column_type == ColumnType.Formula:
+ col_array = self._df_store.ns_fast_access.get(col_def.col_id)
+ if col_array is None or row_index >= len(col_array):
+ return NotStr('—')
+ value = col_array[row_index]
+ # Display error strings produced by the evaluator (#ERR, #DIV/0!, …)
+ if isinstance(value, str) and value.startswith("#"):
+ return NotStr(
+ f'{html.escape(value)}'
+ )
+ # Infer number vs text style from the actual computed value type
+ if isinstance(value, (int, float)) and not isinstance(value, bool):
+ column_type = ColumnType.Number
+ else:
+ column_type = ColumnType.Text
+ else:
+ value = self._df_store.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)
@@ -722,8 +740,9 @@ class DataGrid(MultipleInstance):
"""
if not col_def.visible:
return None
-
- value = self._df_store.ns_fast_access[col_def.col_id][row_index]
+
+ col_array = self._df_store.ns_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)
return OptimizedDiv(content,
diff --git a/src/myfasthtml/controls/DataGridColumnsManager.py b/src/myfasthtml/controls/DataGridColumnsManager.py
index 9c3d437..347f030 100644
--- a/src/myfasthtml/controls/DataGridColumnsManager.py
+++ b/src/myfasthtml/controls/DataGridColumnsManager.py
@@ -41,8 +41,8 @@ class Commands(BaseCommands):
self._owner.show_all_columns).htmx(target=f"#{self._id}", swap="innerHTML")
def save_column_details(self, col_id):
- return Command(f"UpdateColumn",
- f"Update column {col_id}",
+ return Command(f"SaveColumnDetails",
+ f"Save column {col_id}",
self._owner,
self._owner.save_column_details,
kwargs={"col_id": col_id}
@@ -71,7 +71,7 @@ class DataGridColumnsManager(MultipleInstance):
self._parent._parent,
self._parent.get_table_name(),
)
- conf = DslEditorConf(save_button=False, line_numbers=False, engine_id=completion_engine.get_id())
+ conf = DslEditorConf(name="formula", save_button=False, line_numbers=False, engine_id=completion_engine.get_id())
self._formula_editor = DataGridFormulaEditor(self, conf=conf, _id=f"{self._id}-formula-editor")
DslsManager.register(completion_engine, FormulaParser())
@@ -102,7 +102,7 @@ class DataGridColumnsManager(MultipleInstance):
col_def.width = int(v)
elif k == "formula":
col_def.formula = v or ""
- self._register_formula(col_def)
+ # self._register_formula(col_def), Will be done in save_column_details()
else:
setattr(col_def, k, v)
@@ -133,7 +133,8 @@ class DataGridColumnsManager(MultipleInstance):
def save_column_details(self, col_id, client_response):
logger.debug(f"save_column_details {col_id=}, {client_response=}")
- self._get_updated_col_def_from_col_id(col_id, client_response, copy=False)
+ col_def = self._get_updated_col_def_from_col_id(col_id, client_response, copy=False)
+ self._register_formula(col_def)
self._parent.save_state()
return self._mk_inner_content()
@@ -149,12 +150,17 @@ class DataGridColumnsManager(MultipleInstance):
return self.mk_column_details(col_def)
def _register_formula(self, col_def) -> None:
- """Register or remove a formula column with the FormulaEngine."""
+ """Register or remove a formula column with the FormulaEngine.
+
+ Registers only when col_def.type is Formula and the formula text is
+ non-empty. Removes the formula in all other cases so the engine stays
+ consistent with the column definition.
+ """
engine = self._parent.get_formula_engine()
if engine is None:
return
table = self._parent.get_table_name()
- if col_def.formula:
+ if col_def.type == ColumnType.Formula and col_def.formula:
try:
engine.set_formula(table, col_def.col_id, col_def.formula)
logger.debug("Registered formula for %s.%s", table, col_def.col_id)
diff --git a/src/myfasthtml/controls/DslEditor.py b/src/myfasthtml/controls/DslEditor.py
index 6129e2f..7eda73b 100644
--- a/src/myfasthtml/controls/DslEditor.py
+++ b/src/myfasthtml/controls/DslEditor.py
@@ -167,7 +167,7 @@ class DslEditor(MultipleInstance):
return Textarea(
self._state.content,
id=f"ta_{self._id}",
- name=f"ta_{self._id}",
+ name=self.conf.name if (self.conf and self.conf.name) else f"ta_{self._id}",
cls="hidden",
)
diff --git a/src/myfasthtml/controls/helpers.py b/src/myfasthtml/controls/helpers.py
index c8d1771..dcd2e3d 100644
--- a/src/myfasthtml/controls/helpers.py
+++ b/src/myfasthtml/controls/helpers.py
@@ -7,7 +7,7 @@ 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
+ checkbox_checked20_filled, math_formula16_regular
from myfasthtml.icons.fluent_p2 import text_bullet_list_square20_regular, text_field20_regular
from myfasthtml.icons.fluent_p3 import calendar_ltr20_regular
@@ -160,4 +160,5 @@ icons = {
ColumnType.Datetime: calendar_ltr20_regular,
ColumnType.Bool: checkbox_checked20_filled,
ColumnType.Enum: text_bullet_list_square20_regular,
+ ColumnType.Formula: math_formula16_regular,
}
diff --git a/src/myfasthtml/core/formula/engine.py b/src/myfasthtml/core/formula/engine.py
index 69367ce..9aa7af8 100644
--- a/src/myfasthtml/core/formula/engine.py
+++ b/src/myfasthtml/core/formula/engine.py
@@ -96,7 +96,9 @@ class FormulaEngine:
# Registers in DAG and raises FormulaCycleError if cycle detected
self._graph.add_formula(table, col, formula)
self._formulas[(table, col)] = formula
-
+ # Mark dirty so the column is computed on the next render
+ self._graph.mark_dirty(table, col)
+
logger.debug("Formula set for %s.%s: %s", table, col, formula_text)
def remove_formula(self, table: str, col: str) -> None:
diff --git a/tests/controls/test_datagrid_columns_manager.py b/tests/controls/test_datagrid_columns_manager.py
index 43f3699..f8f84af 100644
--- a/tests/controls/test_datagrid_columns_manager.py
+++ b/tests/controls/test_datagrid_columns_manager.py
@@ -37,6 +37,9 @@ class MockDataGrid(MultipleInstance):
def get_table_name(self):
return "mock_table"
+ def get_formula_engine(self):
+ return None
+
@pytest.fixture
def mock_datagrid(root_instance):