Files
MyFastHtml/docs/DataGrid Formatting.md

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:

  • style and formatter can appear alone (unconditional formatting)
  • condition cannot appear alone - must be paired with style and/or formatter
  • If condition is present, the style/formatter is 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:

  1. Specificity = number of conditions in the rule
  2. Higher specificity wins
  3. 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

  1. If preset is specified, apply all preset properties
  2. 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 with empty_label
  • allow_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.

  • row parameter for column-level conditions: Evaluate condition on a specific row
  • AND/OR conditions: Add explicit and/or operators if between/in prove 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_on and filter_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 FormattingEngine to DataGrid.mk_body_cell_content()