diff --git a/src/myfasthtml/assets/core/myfasthtml.js b/src/myfasthtml/assets/core/myfasthtml.js
index ae3ca8f..5b1057a 100644
--- a/src/myfasthtml/assets/core/myfasthtml.js
+++ b/src/myfasthtml/assets/core/myfasthtml.js
@@ -375,7 +375,7 @@ function triggerHtmxAction(elementId, config, combinationStr, isInside, event) {
}
// Make AJAX call with htmx
- console.debug(`Triggering HTMX action for element ${elementId}: ${method} ${url}`, htmxOptions);
+ //console.debug(`Triggering HTMX action for element ${elementId}: ${method} ${url}`, htmxOptions);
htmx.ajax(method, url, htmxOptions);
}
diff --git a/src/myfasthtml/controls/CycleStateControl.py b/src/myfasthtml/controls/CycleStateControl.py
index ebb8ad9..9b22187 100644
--- a/src/myfasthtml/controls/CycleStateControl.py
+++ b/src/myfasthtml/controls/CycleStateControl.py
@@ -1,3 +1,5 @@
+import logging
+
from fasthtml.components import Div
from myfasthtml.controls.BaseCommands import BaseCommands
@@ -6,6 +8,7 @@ from myfasthtml.core.commands import Command
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import MultipleInstance
+logger = logging.getLogger("CycleStateControl")
class CycleState(DbObject):
def __init__(self, owner, save_state):
@@ -24,7 +27,7 @@ class Commands(BaseCommands):
class CycleStateControl(MultipleInstance):
def __init__(self, parent, controls: dict, _id=None, save_state=True):
- super().__init__(parent, _id)
+ super().__init__(parent, _id=_id)
self._state = CycleState(self, save_state)
self.controls_by_states = controls
self.commands = Commands(self)
@@ -34,6 +37,7 @@ class CycleStateControl(MultipleInstance):
self._state.state = next(iter(controls.keys()))
def cycle_state(self):
+ logger.debug(f"cycle_state datagrid={self._parent.get_table_name()}")
keys = list(self.controls_by_states.keys())
current_idx = keys.index(self._state.state)
self._state.state = keys[(current_idx + 1) % len(keys)]
diff --git a/src/myfasthtml/controls/DataGrid.py b/src/myfasthtml/controls/DataGrid.py
index 9cc68c3..3ab6893 100644
--- a/src/myfasthtml/controls/DataGrid.py
+++ b/src/myfasthtml/controls/DataGrid.py
@@ -203,7 +203,10 @@ class DataGrid(MultipleInstance):
"column": mk.icon(column, tooltip="Column selection"),
"cell": mk.icon(grid, tooltip="Cell selection")
}
- self._selection_mode_selector = CycleStateControl(self, controls=selection_types, save_state=False)
+ self._selection_mode_selector = CycleStateControl(self,
+ controls=selection_types,
+ save_state=False,
+ _id="-cycle_state")
self._selection_mode_selector.bind_command("CycleState", self.commands.change_selection_mode())
# add columns manager
diff --git a/src/myfasthtml/controls/DataGridsManager.py b/src/myfasthtml/controls/DataGridsManager.py
index c0492ab..7dc67d9 100644
--- a/src/myfasthtml/controls/DataGridsManager.py
+++ b/src/myfasthtml/controls/DataGridsManager.py
@@ -167,9 +167,12 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
def list_column_values(self, table_name, column_name):
return self._registry.get_column_values(table_name, column_name)
-
+
def get_row_count(self, table_name):
return self._registry.get_row_count(table_name)
+
+ def get_column_type(self, table_name, column_name):
+ return self._registry.get_column_type(table_name, column_name)
def list_style_presets(self) -> list[str]:
return list(self.style_presets.keys())
diff --git a/src/myfasthtml/core/DataGridsRegistry.py b/src/myfasthtml/core/DataGridsRegistry.py
index 255c430..d7f408c 100644
--- a/src/myfasthtml/core/DataGridsRegistry.py
+++ b/src/myfasthtml/core/DataGridsRegistry.py
@@ -59,15 +59,42 @@ class DataGridsRegistry(SingleInstance):
try:
as_fullname_dict = self._get_entries_as_full_name_dict()
grid_id = as_fullname_dict[table_name]
-
+
# load dataframe
state_id = f"{grid_id}#state"
state = self._db_manager.load(state_id)
df = state["ne_df"] if state else None
return len(df) if df is not None else 0
-
+
except KeyError:
return 0
+
+ def get_column_type(self, table_name, column_name):
+ """
+ Get the type of a column.
+
+ Args:
+ table_name: The DataGrid name
+ column_name: The column name
+
+ Returns:
+ ColumnType enum value or None if not found
+ """
+ try:
+ as_fullname_dict = self._get_entries_as_full_name_dict()
+ grid_id = as_fullname_dict[table_name]
+
+ # load datagrid state
+ state_id = f"{grid_id}#state"
+ state = self._db_manager.load(state_id)
+
+ if state and "columns" in state:
+ for col in state["columns"]:
+ if col.col_id == column_name:
+ return col.type
+ return None
+ except KeyError:
+ return None
def _get_all_entries(self):
return {k: v for k, v in self._db_manager.load(DATAGRIDS_REGISTRY_ENTRY_KEY).items()
diff --git a/src/myfasthtml/core/commands.py b/src/myfasthtml/core/commands.py
index fec03bb..a69ddc0 100644
--- a/src/myfasthtml/core/commands.py
+++ b/src/myfasthtml/core/commands.py
@@ -48,10 +48,13 @@ class Command:
# either there is no parameter (so one single instance of the command is enough)
# or the parameter is a kwargs (so the parameters are provided when the command is called)
if key is None:
- if owner is not None and args is None: # args is not provided
- key = f"{owner.get_full_id()}-{name}"
- else:
- key = f"{name}-{_compute_from_args()}"
+ key_parts = []
+ if owner is not None:
+ key_parts.append(f"{owner.get_full_id()}")
+ key_parts.append(name)
+ if args:
+ key_parts.append(_compute_from_args())
+ key = "-".join(key_parts)
else:
key = key.replace("#{args}", _compute_from_args())
if owner is not None:
@@ -122,6 +125,8 @@ class Command:
def execute(self, client_response: dict = None):
logger.debug(f"Executing command {self.name} with arguments {client_response=}")
+ if self._htmx_extra.get("hx-target", "").startswith("#tsm_"):
+ logger.warning(f" Command {self.name} needs a selection manager to work properly.")
with ObservableResultCollector(self._bindings) as collector:
kwargs = self._create_kwargs(self.default_kwargs,
client_response,
@@ -145,6 +150,10 @@ class Command:
and r.get("id", None) is not None):
r.attrs["hx-swap-oob"] = r.attrs.get("hx-swap-oob", "true")
+ if self._htmx_extra.get("hx-target", "").startswith("#tsm_"):
+ ret_debug = [f"<{r.tag} id={r.attrs.get('id', '')}/>" if r else "None" for r in all_ret]
+ logger.warning(f" {ret_debug=}")
+
return all_ret[0] if len(all_ret) == 1 else all_ret
def htmx(self, target: Optional[str] = "this", swap="outerHTML", trigger=None, auto_swap_oob=True):
diff --git a/src/myfasthtml/core/dbmanager.py b/src/myfasthtml/core/dbmanager.py
index 76a79d7..ebdcaa7 100644
--- a/src/myfasthtml/core/dbmanager.py
+++ b/src/myfasthtml/core/dbmanager.py
@@ -79,11 +79,10 @@ class DbObject:
return # still under initialization
if self._reload_self():
- logger.debug(f"finalize_initialization ({self._name}) : Loaded existing content.")
+ # logger.debug(f"finalize_initialization ({self._name}) : Loaded existing content.")
return
else:
- logger.debug(
- f"finalize_initialization ({self._name}) : No existing content found, creating new entry {self._save_state=}.")
+ # logger.debug(f"finalize_initialization ({self._name}) : No existing content found, creating new entry {self._save_state=}.")
self._save_self()
def _reload_self(self):
diff --git a/src/myfasthtml/core/formatting/dsl/completion/FormattingCompletionEngine.py b/src/myfasthtml/core/formatting/dsl/completion/FormattingCompletionEngine.py
index 4457905..9582d88 100644
--- a/src/myfasthtml/core/formatting/dsl/completion/FormattingCompletionEngine.py
+++ b/src/myfasthtml/core/formatting/dsl/completion/FormattingCompletionEngine.py
@@ -181,8 +181,8 @@ class FormattingCompletionEngine(BaseCompletionEngine):
return presets.COMPARISON_OPERATORS
case Context.OPERATOR_VALUE | Context.BETWEEN_VALUE:
- # col., True, False + column values
- base = presets.OPERATOR_VALUE_BASE.copy()
+ # Filter base suggestions according to column type
+ base = self._get_base_suggestions_for_column_type(scope)
base.extend(self._get_column_value_suggestions(scope))
return base
@@ -363,6 +363,44 @@ class FormattingCompletionEngine(BaseCompletionEngine):
except Exception:
return []
+ def _get_base_suggestions_for_column_type(self, scope: DetectedScope) -> list[Suggestion]:
+ """
+ Filter base suggestions according to column type.
+
+ Only suggests True/False for Boolean columns.
+ Always suggests col. for cross-column comparisons.
+
+ Args:
+ scope: The detected scope containing column information
+
+ Returns:
+ List of filtered base suggestions
+ """
+ from myfasthtml.core.constants import ColumnType
+
+ if not scope.column_name:
+ # No column context, return all base suggestions
+ return presets.OPERATOR_VALUE_BASE.copy()
+
+ try:
+ table_name = scope.table_name or self.table_name or ""
+ col_type = self.provider.get_column_type(table_name, scope.column_name)
+
+ suggestions = []
+
+ # Always suggest col. for cross-column comparisons
+ suggestions.append(Suggestion("col.", "Reference another column", "keyword"))
+
+ # Add True/False only for Boolean columns
+ if col_type == ColumnType.Bool:
+ suggestions.append(Suggestion("True", "Boolean true", "literal"))
+ suggestions.append(Suggestion("False", "Boolean false", "literal"))
+
+ return suggestions
+ except Exception:
+ # Fallback to default if type detection fails
+ return presets.OPERATOR_VALUE_BASE.copy()
+
def get_completions(
text: str,
diff --git a/src/myfasthtml/core/formatting/dsl/completion/provider.py b/src/myfasthtml/core/formatting/dsl/completion/provider.py
index a1592e3..b48fef7 100644
--- a/src/myfasthtml/core/formatting/dsl/completion/provider.py
+++ b/src/myfasthtml/core/formatting/dsl/completion/provider.py
@@ -72,3 +72,18 @@ class DatagridMetadataProvider(BaseMetadataProvider):
Number of rows
"""
...
+
+ def get_column_type(self, table_name: str, column_name: str):
+ """
+ Return the type of a column.
+
+ Used to filter suggestions based on column type.
+
+ Args:
+ table_name: The DataGrid name
+ column_name: The column name
+
+ Returns:
+ ColumnType enum value or None if not found
+ """
+ ...
diff --git a/src/myfasthtml/core/instances.py b/src/myfasthtml/core/instances.py
index 2c66d0f..662a974 100644
--- a/src/myfasthtml/core/instances.py
+++ b/src/myfasthtml/core/instances.py
@@ -6,6 +6,8 @@ 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
+VERBOSE_VERBOSE = False
+
logger = logging.getLogger("InstancesManager")
special_session = {
@@ -26,12 +28,19 @@ class BaseInstance:
def __new__(cls, *args, **kwargs):
# Extract arguments from both positional and keyword arguments
# Signature matches __init__: parent, session=None, _id=None, auto_register=True
+ if VERBOSE_VERBOSE:
+ logger.debug(f"Creating new instance of type {cls.__name__}")
+
parent = args[0] if len(args) > 0 and isinstance(args[0], BaseInstance) else kwargs.get("parent", None)
session = args[1] if len(args) > 1 and isinstance(args[1], dict) else kwargs.get("session", None)
_id = args[2] if len(args) > 2 and isinstance(args[2], str) else kwargs.get("_id", None)
+ if VERBOSE_VERBOSE:
+ logger.debug(f" parent={parent}, session={session}, _id={_id}")
# Compute _id
_id = cls.compute_id(_id, parent)
+ if VERBOSE_VERBOSE:
+ logger.debug(f" computed id={_id}")
if session is None:
if parent is not None:
@@ -46,9 +55,14 @@ class BaseInstance:
res = InstancesManager.instances[key]
if type(res) is not cls:
raise TypeError(f"Instance with id {_id} already exists, but is of type {type(res)}")
+
+ if VERBOSE_VERBOSE:
+ logger.debug(f" instance {_id} already exists, returning existing instance")
return res
# Otherwise create a new instance
+ if VERBOSE_VERBOSE:
+ logger.debug(f" creating new instance")
instance = super().__new__(cls)
instance._is_new_instance = True # mark as fresh
return instance