Skip to content

Dynamic forms

Parts of a form can appear, disappear or change with what the user entered. Three combinators cover it.

A part that depends on a value

Form.andThen builds the rest of the form from the value of the first part. The function runs when that value changes, and the previous part is thrown away.

open Fable.Ripple
open Fable.Ripple.Dom
open Fable.Ripple.Form
open Fable.Ripple.Form.Plain

[<RequireQualifiedAccess>]
type Contact =
    | Email
    | Phone

    interface RadioField.OptionItem with
        member this.Key =
            match this with
            | Email -> "email"
            | Phone -> "phone"

        member this.Text =
            match this with
            | Email -> "Email"
            | Phone -> "Phone"

let contact: Var<RadioField.OptionItem option> = Var.create None
let email = Var.create ""
let phone = Var.create ""

let form =
    RadioField.create "contact"
    |> RadioField.withLabel "Contact me by"
    |> RadioField.withOptions
        [
            Contact.Email
            Contact.Phone
        ]
    |> Field.create
        contact
        (fun value ->
            match value with
            | Some item -> Ok(item :?> Contact)
            | None -> Error "Choose one"
        )
    |> Form.radioField
    |> Form.andThen (
        function
        | Contact.Email ->
            TextField.create "email"
            |> TextField.withLabel "Email"
            |> Field.create email Ok
            |> Form.textField
        | Contact.Phone ->
            TextField.create "phone"
            |> TextField.withLabel "Phone"
            |> Field.create phone Ok
            |> Form.textField
    )

let state = Var.create View.Idle

Html.mount
    "app"
    (Form.View.asHtml
        {
            OnSubmit = fun value -> state.Value <- View.Success $"Contact: %s{value}"
            State = state
            ErrorVisibility = View.errorVisibility ()
            Action = View.Action.SubmitOnly "Save"
            Validation = ValidateOnSubmit
        }
        form)

Pick Email, type an address, pick Phone, then Email again: the address is still there. The email field was rebuilt, but its value lives in the email Var, which was not.

The result of the whole form is the result of the part that is currently shown. A branch that needs several fields is a form of its own, built with Form.succeed and Form.append, and Form.section gives it a frame and a title.

A field that shows on a condition

Form.showIf takes a condition and a form. While the condition holds the form is there; otherwise it renders nothing, yields None, and cannot fail. The condition is a tracked read, so a Var read inside it drives it.

open Fable.Ripple
open Fable.Ripple.Dom
open Fable.Ripple.Form
open Fable.Ripple.Form.Plain

let hasPet = Var.create false
let petName = Var.create ""

let form =
    Form.succeed (fun _ petName -> defaultArg petName "no pet")
    |> Form.append (
        CheckboxField.create "has-pet"
        |> CheckboxField.withText "I have a pet"
        |> Field.create hasPet Ok
        |> Form.checkboxField
    )
    |> Form.append (
        TextField.create "pet-name"
        |> TextField.withLabel "Its name"
        |> Field.create petName Ok
        |> Form.textField
        |> Form.showIf (fun () -> hasPet.Value)
    )

let state = Var.create View.Idle

Html.mount
    "app"
    (Form.View.asHtml
        {
            OnSubmit = fun pet -> state.Value <- View.Success pet
            State = state
            ErrorVisibility = View.errorVisibility ()
            Action = View.Action.SubmitOnly "Save"
            Validation = ValidateOnSubmit
        }
        form)

Tick the box: a required field appears. Untick it: the form is valid again. Tick it once more: the name you typed is back.

Disabling on a condition

Form.disableIf and Form.readOnlyIf keep the field visible and its value in the result, but the user cannot change it while the condition holds:

notesField |> Form.disableIf (fun () -> locked.Value)

Which one to use

  • The shape of the rest of the form depends on a value: andThen.
  • One field or group comes and goes: showIf.
  • The field stays, the user just cannot edit it: disableIf.
Edit this page