16 KiB
DataGrid Formatting
Implementation Status
| Component | Status | Location |
|---|---|---|
| Core Module | src/myfasthtml/core/formatting/ |
|
| Dataclasses (Condition, Style, Formatter, FormatRule) | ✅ Implemented | dataclasses.py |
| Style Presets (DaisyUI 5) | ✅ Implemented | presets.py |
| Formatter Presets (EUR, USD, etc.) | ✅ Implemented | presets.py |
| ConditionEvaluator (12 operators) | ✅ Implemented | condition_evaluator.py |
| StyleResolver | ✅ Implemented | style_resolver.py |
| FormatterResolver (Number, Date, Boolean, Text, Enum) | ✅ Implemented | formatter_resolver.py |
| FormattingEngine (facade + conflict resolution) | ✅ Implemented | engine.py |
| Condition Features | ||
col parameter (row-level conditions) |
✅ Implemented | |
row parameter (column-level conditions) |
❌ Not implemented | |
Column reference in value {"col": "..."} |
✅ Implemented | |
| DataGrid Integration | ||
Integration in mk_body_cell_content() |
❌ Not implemented | |
| DataGridsManager (global presets) | ✅ Implemented | DataGridsManager.py |
| Tests | tests/core/formatting/ |
|
| test_condition_evaluator.py | ✅ ~45 test cases | |
| test_style_resolver.py | ✅ ~12 test cases | |
| test_formatter_resolver.py | ✅ ~40 test cases | |
| test_engine.py | ✅ ~18 test cases |
Overview
This document describes the formatting capabilities for the DataGrid component.
Formatting applies at three levels:
| Level | Cells Targeted | Condition Evaluated On |
|---|---|---|
| Cell | 1 specific cell | The cell value |
| Row | All cells in the row | Each cell value (or fixed column with col) |
| Column | All cells in the column | Each cell value (or fixed row with row) |
Format Rule Structure
A format is a list of rules. Each rule is an object:
{
"condition": {},
"style": {},
"formatter": {}
}
Rules:
styleandformattercan appear alone (unconditional formatting)conditioncannot appear alone - must be paired withstyleand/orformatter- If
conditionis present, thestyle/formatteris applied only if the condition is met - Rules are evaluated in order; multiple rules can match
Conflict Resolution
When multiple rules match the same cell:
- Specificity = number of conditions in the rule
- Higher specificity wins
- At equal specificity, last rule wins entirely (no fusion)
[
{
"style": {
"color": "gray"
}
},
{
"condition": {
"operator": "<",
"value": 0
},
"style": {
"color": "red"
}
},
{
"condition": {
"operator": "==",
"value": -5
},
"style": {
"color": "black"
}
}
]
For value = -5: Rule 3 wins (same specificity as rule 2, but defined later).
Condition Structure
Fields
| Field | Type | Default | Required | Description | Status |
|---|---|---|---|---|---|
operator |
string | - | Yes | Comparison operator | ✅ |
value |
scalar / list / object | - | Depends | Value to compare against | ✅ |
not |
bool | false |
No | Inverts the condition result | ✅ (as negate) |
case_sensitive |
bool | false |
No | Case-sensitive string comparison | ✅ |
col |
string | - | No | Reference column (for row-level conditions) | ✅ |
row |
int | - | No | Reference row (for column-level conditions) | ❌ Not implemented |
Operators
All operators are ✅ implemented.
| Operator | Description | Value Required |
|---|---|---|
== |
Equal | Yes |
!= |
Not equal | Yes |
< |
Less than | Yes |
<= |
Less than or equal | Yes |
> |
Greater than | Yes |
>= |
Greater than or equal | Yes |
contains |
String contains | Yes |
startswith |
String starts with | Yes |
endswith |
String ends with | Yes |
in |
Value in list | Yes (list) |
between |
Value between two values | Yes ([min, max]) |
isempty |
Value is empty/null | No |
isnotempty |
Value is not empty/null | No |
Value Types
Literal value:
{
"operator": "<",
"value": 0
}
{
"operator": "in",
"value": [
"A",
"B",
"C"
]
}
Cell reference (compare with another column):
{
"operator": ">",
"value": {
"col": "budget"
}
}
Negation
Use the not flag instead of separate operators:
{
"operator": "in",
"value": [
"A",
"B"
],
"not": true
}
{
"operator": "contains",
"value": "error",
"not": true
}
Case Sensitivity
String comparisons are case-insensitive by default.
{
"operator": "==",
"value": "Error",
"case_sensitive": true
}
Evaluation Behavior
| Situation | Behavior |
|---|---|
Cell value is null |
Condition = false |
Referenced cell is null |
Condition = false |
| Type mismatch | Condition = false (no coercion) |
| String operators | Converts value to string first |
Examples
// Row-level: highlight if "status" column == "error"
{
"col": "status",
"operator": "==",
"value": "error"
}
// Column-level: bold if row 0 has value "Total"
{
"row": 0,
"operator": "==",
"value": "Total"
}
// Compare with another column
{
"operator": ">",
"value": {
"col": "budget"
}
}
// Negated condition
{
"operator": "in",
"value": [
"draft",
"pending"
],
"not": true
}
Style Structure
✅ Fully implemented in style_resolver.py
Fields
| Field | Type | Default | Description |
|---|---|---|---|
preset |
string | - | Preset name (applied first, can be overridden) |
background_color |
string | - | Background color (hex, CSS name, or CSS variable) |
color |
string | - | Text color |
font_weight |
string | "normal" |
"normal" or "bold" |
font_style |
string | "normal" |
"normal" or "italic" |
font_size |
string | - | Font size ("12px", "0.9em") |
text_decoration |
string | "none" |
"none", "underline", "line-through" |
Example
{
"style": {
"preset": "success",
"font_weight": "bold"
}
}
Default Presets (DaisyUI 5)
| Preset | Background | Text |
|---|---|---|
primary |
var(--color-primary) |
var(--color-primary-content) |
secondary |
var(--color-secondary) |
var(--color-secondary-content) |
accent |
var(--color-accent) |
var(--color-accent-content) |
neutral |
var(--color-neutral) |
var(--color-neutral-content) |
info |
var(--color-info) |
var(--color-info-content) |
success |
var(--color-success) |
var(--color-success-content) |
warning |
var(--color-warning) |
var(--color-warning-content) |
error |
var(--color-error) |
var(--color-error-content) |
All presets default to font_weight: "normal", font_style: "normal", text_decoration: "none".
Resolution Logic
- If
presetis specified, apply all preset properties - Override with any explicit properties
No style fusion: When multiple rules match, the winning rule's style applies entirely.
Formatter Structure
Formatters transform cell values for display without changing the underlying data.
Usage
{
"formatter": {
"preset": "EUR"
}
}
{
"formatter": {
"preset": "EUR",
"precision": 3
}
}
Error Handling
If formatting fails (e.g., non-numeric value for number formatter), display "⚠".
Formatter Types
All formatter types are ✅ implemented in formatter_resolver.py.
number
For numbers, currencies, and percentages.
| Property | Type | Default | Description |
|---|---|---|---|
prefix |
string | "" |
Text before value |
suffix |
string | "" |
Text after value |
thousands_sep |
string | "" |
Thousands separator |
decimal_sep |
string | "." |
Decimal separator |
precision |
int | 0 |
Number of decimal places |
multiplier |
number | 1 |
Multiply value before display |
date
For dates and datetimes.
| Property | Type | Default | Description |
|---|---|---|---|
format |
string | "%Y-%m-%d" |
strftime format pattern |
boolean
For true/false values.
| Property | Type | Default | Description |
|---|---|---|---|
true_value |
string | "true" |
Display for true |
false_value |
string | "false" |
Display for false |
null_value |
string | "" |
Display for null |
text
For text transformations.
| Property | Type | Default | Description |
|---|---|---|---|
transform |
string | - | "uppercase", "lowercase", "capitalize" |
max_length |
int | - | Truncate if exceeded |
ellipsis |
string | "..." |
Suffix when truncated |
enum
For mapping values to display labels. Also used for Select dropdowns.
| Property | Type | Default | Description |
|---|---|---|---|
source |
object | - | Data source (see below) |
default |
string | "" |
Label for unknown values |
allow_empty |
bool | true |
Show empty option in Select |
empty_label |
string | "-- Select --" |
Label for empty option |
order_by |
string | "source" |
"source", "display", "value" |
Source Types
Static mapping: ✅ Implemented
{
"type": "enum",
"source": {
"type": "mapping",
"value": {
"draft": "Brouillon",
"pending": "En attente",
"approved": "Approuvé"
}
},
"default": "Inconnu"
}
From another DataGrid: ✅ Implemented (requires lookup_resolver injection)
{
"type": "enum",
"source": {
"type": "datagrid",
"value": "categories_grid",
"value_column": "id",
"display_column": "name"
}
}
Empty Value Behavior
allow_empty: true→ Empty option displayed withempty_labelallow_empty: false→ First entry selected by default
Default Formatter Presets
formatter_presets = {
"EUR": {
"type": "number",
"suffix": " €",
"thousands_sep": " ",
"decimal_sep": ",",
"precision": 2
},
"USD": {
"type": "number",
"prefix": "$",
"thousands_sep": ",",
"decimal_sep": ".",
"precision": 2
},
"percentage": {
"type": "number",
"suffix": "%",
"precision": 1,
"multiplier": 100
},
"short_date": {
"type": "date",
"format": "%d/%m/%Y"
},
"iso_date": {
"type": "date",
"format": "%Y-%m-%d"
},
"yes_no": {
"type": "boolean",
"true_value": "Yes",
"false_value": "No"
}
}
Storage Architecture
⚠️ Structures exist but integration with formatting engine not implemented
Format Storage Location
| Level | Storage | Key | Status |
|---|---|---|---|
| Column | DataGridColumnState.format |
- | Structure exists |
| Row | DataGridRowState.format |
- | Structure exists |
| Cell | DatagridState.cell_formats |
Cell ID | Structure exists |
Cell ID Format
tcell_{datagrid_id}-{row_index}-{col_index}
DataGridsManager
✅ Implemented in src/myfasthtml/controls/DataGridsManager.py
Global presets stored as instance attributes:
| Property | Type | Description | Status |
|---|---|---|---|
style_presets |
dict | Style presets (primary, success, etc.) | ✅ |
formatter_presets |
dict | Formatter presets (EUR, percentage, etc.) | ✅ |
default_locale |
string | Default locale for number/date formatting | ❌ Not implemented |
Methods:
| Method | Description |
|---|---|
get_style_presets() |
Get the global style presets |
get_formatter_presets() |
Get the global formatter presets |
add_style_preset(name, preset) |
Add or update a style preset |
add_formatter_preset(name, preset) |
Add or update a formatter preset |
remove_style_preset(name) |
Remove a style preset |
remove_formatter_preset(name) |
Remove a formatter preset |
Usage:
# Add custom presets
manager.add_style_preset("highlight", {"background-color": "yellow", "color": "black"})
manager.add_formatter_preset("CHF", {"type": "number", "prefix": "CHF ", "precision": 2})
Future Considerations
All items below are ❌ not implemented.
rowparameter for column-level conditions: Evaluate condition on a specific row- AND/OR conditions: Add explicit
and/oroperators ifbetween/inprove insufficient - Cell references: Extend to
{"col": "x", "row": 0}for specific cell and{"col": "x", "row_offset": -1}for relative references - Enum cascade (draft): Dependent dropdowns with
depends_onandfilter_column{ "source": { "type": "datagrid", "value": "cities_grid", "value_column": "id", "display_column": "name", "filter_column": "country_id" }, "depends_on": "country" } - API source for enum:
{"type": "api", "value": "https://...", ...} - Searchable enum: For large option lists
- Formatter chaining: Apply multiple formatters in sequence
- DataGrid integration: Connect
FormattingEnginetoDataGrid.mk_body_cell_content()