Customize the view
You can provide your own markup for any built-in field.
Write a render function for the same attributes and a constructor with the same name, then shadow the built-in field by opening your module after Fable.Ripple.Form.Plain.
Form.textField then refers to your implementation, while the rest of Form stays the package's.
This page gives the text field a different layout: the label above the control, the error under it, in your own classes.
Write the render function
Same signature as the package's
TextField.render. Four things wire a control into the form:View.controlAttributesfor the id and the ARIA attributes.config.OnBluron the control'sblurevent.config.Disabledandconfig.ReadOnlyon the control.View.shownErrorfor the message to display,Noneuntil the validation mode says to show it.
module Fields = let text (config: FieldRenderConfig<string, TextField.Attributes>) : DomItem = Html.div [ attr.className "my-field" Html.p [ attr.className "my-field__label" View.labelText config.Attributes.Label config ] Html.input [ View.controlAttributes config attr.className "my-field__input" attr.bindValue config.Value on.blur (fun _ -> config.OnBlur()) attr.disabled config.Disabled attr.readOnly config.ReadOnly ] Html.p [ attr.className "my-field__error" Html.text (fun () -> View.shownError config () |> Option.defaultValue "") ] ]Shadow the constructor
Same name and same empty check as the package's, with your render function.
[<RequireQualifiedAccess>] module Form = let textField (config: FieldConfig<TextField.Attributes, string, 'Output>) : Form<'Output> = Base.field System.String.IsNullOrEmpty Fields.text config
Using it
Both pieces together, in a form. The text field has the new markup; the number field is still the package's.
open Fable.Ripple
open Fable.Ripple.Dom
open Fable.Ripple.Form
open Fable.Ripple.Form.Plain
module Fields =
let text (config: FieldRenderConfig<string, TextField.Attributes>) : DomItem =
Html.div
[
attr.className "my-field"
Html.p
[
attr.className "my-field__label"
View.labelText config.Attributes.Label config
]
Html.input
[
View.controlAttributes config
attr.className "my-field__input"
attr.bindValue config.Value
on.blur (fun _ -> config.OnBlur())
attr.disabled config.Disabled
attr.readOnly config.ReadOnly
]
Html.p
[
attr.className "my-field__error"
Html.text (fun () -> View.shownError config () |> Option.defaultValue "")
]
]
[<RequireQualifiedAccess>]
module Form =
let textField (config: FieldConfig<TextField.Attributes, string, 'Output>) : Form<'Output> =
Base.field System.String.IsNullOrEmpty Fields.text config
let name = Var.create ""
let age = Var.create ""
let form =
Form.succeed (fun name age -> name, age)
|> Form.append (
TextField.create "name"
|> TextField.withLabel "Name"
|> Field.create
name
(fun value ->
if value.Length < 2 then
Error "At least 2 characters"
else
Ok value
)
|> Form.textField
)
|> Form.append (
NumberField.create "age"
|> NumberField.withLabel "Age"
|> Field.create
age
(fun value ->
match System.Int32.TryParse value with
| true, age -> Ok age
| _ -> Error "A whole number"
)
|> Form.numberField
)
let state = Var.create View.Idle
Html.mount
"app"
(Form.View.asHtml
{
OnSubmit = fun (name, age) -> state.Value <- View.Success $"%s{name}, %d{age}"
State = state
ErrorVisibility = View.errorVisibility ()
Action = View.Action.SubmitOnly "Save"
Validation = ValidateOnBlur
}
form)