The description below is AI-generated, so take it with a grian of salt, but it points in the right direction.
Summary
Add support for action/submit buttons as first-class form elements with callbacks, while maintaining effect-agnostic design.
Motivation
Currently, submit buttons must be handled outside the form system. Users need to:
- Manually render buttons in the app layer
- Wire up
SubmitAttempted messages
- Handle form disabling during async operations manually
Proposed Design
Option A: Message-based (Elm-style) - Recommended
Keep buttons pure, emit typed messages that the app layer handles:
case class ActionButton(
id: String,
label: String,
style: ButtonStyle = ButtonStyle.Primary // Primary, Secondary, Danger, etc.
) extends FormElement
// In FormElementUpdate
case class ActionTriggered(buttonId: String) extends FormElementUpdate
App handles the effect:
case Msg.FormUpdated(ActionTriggered("submit")) =>
(model.copy(formState = formState.setSubmitting), submitToServer())
Pros: Simple, fully effect-agnostic, consistent with current architecture
Cons: Callback logic lives in app, not with button definition
Option B: Callback with type parameter
Button carries a callback returning the app's message type:
case class ActionButton[Msg](
id: String,
label: String,
onClick: () => Msg,
disableFormWhileProcessing: Boolean = true
) extends FormElement
Pros: Callback co-located with button definition
Cons: Introduces type parameter, complicates derivation and state management
Option C: Effect-parameterized (tagless-style)
case class ActionButton[F[_]](
id: String,
label: String,
onClick: F[Unit]
)
Pros: Explicit effect abstraction
Cons: Over-engineered for forms, complicates the API significantly
Form Disabling During Submission
Reuse LoadingState pattern from DataTable:
// New FormState wrapper or extension to root FormElementState
case class FormState(
root: FormElementState,
submissionState: SubmissionState = SubmissionState.Idle
)
enum SubmissionState {
case Idle
case Submitting(actionId: String)
case Failed(error: String)
}
Renderers check submissionState to disable inputs and show loading indicators.
Derivation
Buttons should not participate in automatic form derivation from case classes. They must be added manually after derivation due to:
- Custom callback logic per button
- Multiple buttons with different behaviors (submit, save draft, cancel)
- Button placement flexibility in the form layout
Example usage:
case class User(name: String, email: String) derives ToFormElem
val form = summon[ToFormElem[User]].get
val formWithButtons = form.withActions(
ActionButton("submit", "Submit"),
ActionButton("draft", "Save Draft", style = ButtonStyle.Secondary)
)
Open Questions
- Should multiple action buttons be supported in a single form?
- How to handle button-specific validation (e.g., "Save Draft" skips required field validation)?
- Should buttons have a
disabled predicate based on form state (e.g., disable submit if form invalid)?
References
- Elm's message-based approach: buttons emit messages, update function handles effects
- Current DataTable server mode pattern:
setLoading -> fetch -> setServerData
The description below is AI-generated, so take it with a grian of salt, but it points in the right direction.
Summary
Add support for action/submit buttons as first-class form elements with callbacks, while maintaining effect-agnostic design.
Motivation
Currently, submit buttons must be handled outside the form system. Users need to:
SubmitAttemptedmessagesProposed Design
Option A: Message-based (Elm-style) - Recommended
Keep buttons pure, emit typed messages that the app layer handles:
App handles the effect:
Pros: Simple, fully effect-agnostic, consistent with current architecture
Cons: Callback logic lives in app, not with button definition
Option B: Callback with type parameter
Button carries a callback returning the app's message type:
Pros: Callback co-located with button definition
Cons: Introduces type parameter, complicates derivation and state management
Option C: Effect-parameterized (tagless-style)
Pros: Explicit effect abstraction
Cons: Over-engineered for forms, complicates the API significantly
Form Disabling During Submission
Reuse
LoadingStatepattern from DataTable:Renderers check
submissionStateto disable inputs and show loading indicators.Derivation
Buttons should not participate in automatic form derivation from case classes. They must be added manually after derivation due to:
Example usage:
Open Questions
disabledpredicate based on form state (e.g., disable submit if form invalid)?References
setLoading-> fetch ->setServerData