Your first form
A sign-up form: an email, a password, its repeat, and a newsletter checkbox.
Submitting gives a User record, and its fields are not strings: an Email can only be made through Email.tryParse, so a User holds a valid address by construction.
type Email = private Email of string
module Email =
let tryParse (value: string) =
if value.Contains "@" then
Ok(Email value)
else
Error "An email needs an @"
let value (Email email) = email
type Password = private Password of string
module Password =
let tryParse (value: string) =
if value.Length < 4 then
Error "At least 4 characters"
else
Ok(Password value)
type User =
{
Email: Email
Password: Password
Newsletter: bool
}
One Var per field
Every field stores what the user typed in a Var. You create them, so you can read or write them from anywhere: set a default, prefill from a server, watch them, etc.
let email = Var.create ""
let password = Var.create ""
let repeat = Var.create ""
let newsletter = Var.create false
A field
A field is built in three steps. The attributes say how it looks; Field.create ties it to its Var and gives it a parser; the constructor picks the field kind.
let emailField =
EmailField.create "email"
|> EmailField.withLabel "Email"
|> Field.create email Email.tryParse
|> Form.emailField
The parser turns the text into your type, or into a message for the user, every time the value changes. Email.tryParse is one already.
A parser can read another field. The repeat field reads password.Value, so it is checked again whenever the password changes:
let repeatField =
PasswordField.create "repeat"
|> PasswordField.withLabel "Repeat password"
|> Field.create
repeat
(fun value ->
if value = password.Value then
Ok()
else
Error "The passwords do not match"
)
|> Form.passwordField
Putting the fields together
Form.succeed takes the function that builds the result. Each Form.append gives it the next field's value, in order. The repeat field's value is not needed, so its argument is _.
let form =
Form.succeed (fun email password _ newsletter ->
{
Email = email
Password = password
Newsletter = newsletter
}
)
|> Form.append emailField
|> Form.append passwordField
|> Form.append repeatField
|> Form.append newsletterField
Showing it
Form.View.asHtml renders the form with a submit button. OnSubmit receives the User once every field parses; until then, submitting shows the errors instead.
open Fable.Ripple
open Fable.Ripple.Dom
open Fable.Ripple.Form
open Fable.Ripple.Form.Plain
type Email = private Email of string
module Email =
let tryParse (value: string) =
if value.Contains "@" then
Ok(Email value)
else
Error "An email needs an @"
let value (Email email) = email
type Password = private Password of string
module Password =
let tryParse (value: string) =
if value.Length < 4 then
Error "At least 4 characters"
else
Ok(Password value)
type User =
{
Email: Email
Password: Password
Newsletter: bool
}
let email = Var.create ""
let password = Var.create ""
let repeat = Var.create ""
let newsletter = Var.create false
let form =
Form.succeed (fun email password _ newsletter ->
{
Email = email
Password = password
Newsletter = newsletter
}
)
|> Form.append (
EmailField.create "email"
|> EmailField.withLabel "Email"
|> Field.create email Email.tryParse
|> Form.emailField
)
|> Form.append (
PasswordField.create "password"
|> PasswordField.withLabel "Password"
|> Field.create password Password.tryParse
|> Form.passwordField
)
|> Form.append (
PasswordField.create "repeat"
|> PasswordField.withLabel "Repeat password"
|> Field.create
repeat
(fun value ->
if value = password.Value then
Ok()
else
Error "The passwords do not match"
)
|> Form.passwordField
)
|> Form.append (
CheckboxField.create "newsletter"
|> CheckboxField.withText "Send me the newsletter"
|> Field.create newsletter Ok
|> Form.checkboxField
)
let state = Var.create View.Idle
Html.mount
"app"
(Form.View.asHtml
{
OnSubmit =
fun user -> state.Value <- View.Success $"Welcome, %s{Email.value user.Email}."
State = state
ErrorVisibility = View.errorVisibility ()
Action = View.Action.SubmitOnly "Sign up"
Validation = ValidateOnBlur
}
form)
Fill the password, then type something else in the repeat: its error appears as soon as you leave the field. Fix the password instead: the repeat's error goes away, because its parser re-ran.
The view config has five parts:
OnSubmit: what to do with the result.State: aVaryou write.Loadingdisables the fields and the button;SuccessandErrorshow a message under the form.ErrorVisibility: which errors are showing. One per form, fromView.errorVisibility ().Action:SubmitOnly "label"for one button, orCustomto draw your own; see Saving and loading.Validation: when a field starts showing its error; see Validation.
Reading the result without submitting
The form is a value with signals: form.Result is the current Ok user or the errors, form.IsEmpty tells whether everything is blank. A "Save" button can be disabled while form.Result.Value is an Error, and a preview can read the record as it is typed.