Skip to content

Add FormFieldConverter and dataOverlayProvider to Form/FormPanel#581

Merged
rjaros merged 1 commit intorjaros:masterfrom
tfonrouge:feature/form-field-converter-data-overlay
Mar 28, 2026
Merged

Add FormFieldConverter and dataOverlayProvider to Form/FormPanel#581
rjaros merged 1 commit intorjaros:masterfrom
tfonrouge:feature/form-field-converter-data-overlay

Conversation

@tfonrouge
Copy link
Copy Markdown
Contributor

Summary

  • FormFieldConverter — per-field converter interface enabling custom type transformations between model values and form control values during setData()/getData(), beyond the built-in String/Number/Boolean/Date/KFiles handling
  • dataOverlayProvider — optional callback on Form/FormPanel that injects external key-value pairs into getData() results, allowing non-FormControl components (tabulators, custom editors) to participate in form data collection

Problem

KVision's form system handles built-in types during serialization, but custom types require workarounds. Additionally, external components have no way to contribute data to a form without implementing FormControl.

Examples

FormFieldConverter — mapping an enum ordinal to a select control:

val converter = object : FormFieldConverter {
    override fun fromJson(jsonValue: dynamic): Any? =
        Priority.entries[jsonValue as Int].name

    override fun toJson(controlValue: Any?): dynamic =
        Priority.valueOf(controlValue as String).ordinal
}
form.add(MyModel::priority, TomSelect(), converter = converter)

FormFieldConverter — via registerConverter():

form.add(MyModel::score, Text())
form.registerConverter(MyModel::score, myConverter)
// later: form.removeConverter(MyModel::score)

dataOverlayProvider — tabulator contributing row data:

formPanel.dataOverlayProvider = {
    mapOf("rows" to tabulator.getData().serialize())
}
// overlay values bypass validation and take precedence over field values

Design decisions

  • FormFieldConverter is a non-generic interface with two methods (fromJson/toJson) — minimal, easy to implement
  • converter parameter added to all add()/bind() overloads (20+) in both Form and FormPanel, placed before lambda parameters following Kotlin's lambda-last convention. This is fully compatible with named-argument callers; positional callers will see a compile error prompting them to add the new parameter.
  • Both features work in serializer (@Serializable classes) and non-serializer (Map-based) form paths
  • Overlay values bypass converter/type dispatch and validation (documented in KDoc). Null overlay values are skipped. Unknown keys cause a deserialization error in serializable forms.
  • Converters are cleaned up on remove()/removeAll() and survive clearData()

Test plan

  • Converter via string-key add()
  • Converter via typed add() (KProperty1 overload)
  • registerConverter() / removeConverter() lifecycle
  • Converter in non-serializer (Map-based) form path
  • Overlay: basic injection, field override, dynamic form
  • clearData() preserves converter registrations
  • FormPanel-level converter delegation
  • getDataJson() with converter

🤖 Generated with Claude Code

Problem:
KVision's form system only handles built-in types (String, Number, Boolean,
Date, List<KFile>) during setData/getData serialization. Custom types require
workarounds, and external components (tabulators, custom editors) have no way
to participate in form data collection without implementing FormControl.

Solution:
1. FormFieldConverter — a per-field converter interface enabling custom type
   transformations between model values and form control values.

   Example — storing a comma-separated list as a single Text field:

     val converter = object : FormFieldConverter {
         override fun fromJson(jsonValue: dynamic): Any? =
             (jsonValue as? String)  // model value: "a,b,c"

         override fun toJson(controlValue: Any?): dynamic =
             (controlValue as? String)  // control value back to model
     }
     form.add(MyModel::tags, Text(), converter = converter)

   Example — mapping an enum ordinal to a select control:

     val converter = object : FormFieldConverter {
         override fun fromJson(jsonValue: dynamic): Any? =
             Priority.entries[jsonValue as Int].name

         override fun toJson(controlValue: Any?): dynamic =
             Priority.valueOf(controlValue as String).ordinal
     }
     form.add(MyModel::priority, TomSelect(), converter = converter)

   Converters can be passed inline via the `converter` parameter on any
   add()/bind() overload, or registered/removed dynamically via
   registerConverter()/removeConverter().

2. dataOverlayProvider — an optional callback on Form/FormPanel that injects
   external key-value pairs into getData() results, allowing non-FormControl
   components to contribute form data.

   Example — a tabulator contributing row data to the form:

     formPanel.dataOverlayProvider = {
         mapOf("rows" to tabulator.getData().serialize())
     }

   Overlay values bypass converter/type dispatch and validation, and take
   precedence over field values on key conflicts.

Both features work in serializer and non-serializer (Map-based) form paths.

The `converter` parameter is placed before the lambda parameters
(`validatorMessage`, `validator`) to follow Kotlin's lambda-last convention.
This is fully compatible with named-argument callers. Positional callers will
see a compile error prompting them to add the new parameter.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
tfonrouge added a commit to tfonrouge/kvision-guide that referenced this pull request Mar 26, 2026
Companion documentation for rjaros/kvision#581.

Adds two new sections to the Forms page:

- **Field converters**: Documents the FormFieldConverter interface as a
  simpler alternative to custom KSerializers for type transformations
  between model values and form control values. Covers inline converter
  via add(), registerConverter()/removeConverter(), and dynamic forms.

- **Data overlay provider**: Documents the dataOverlayProvider property
  for injecting external component data into getData() results without
  implementing FormControl. Covers basic usage, precedence behavior,
  and validation/null/unknown-key caveats.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@rjaros rjaros merged commit 98e31ac into rjaros:master Mar 28, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants