Skip to content

Fields

Every field is built the same way, whatever its kind.

  1. Start from the attributes

    create takes the field's id; the with* builders say how it looks. Each kind has its own type: TextField, NumberField, SelectField, and so on.

    TextField.create "product" |> TextField.withLabel "Product"
    
  2. Add the value and the parser

    Field.create ties the attributes to a Var and a parser. The parser turns what the user typed into your type, or into a message to show. Pass Ok when the text is the value.

    |> Field.create name (fun value -> if value = "" then Error "Required" else Ok value)
    
  3. Close with the constructor

    The constructor names the kind, and turns the whole thing into a form of one field.

    |> Form.textField
    

All the kinds in one form:

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

[<RequireQualifiedAccess>]
type Size =
    | Small
    | Large

    interface SelectField.OptionItem with
        member this.Key =
            match this with
            | Small -> "s"
            | Large -> "l"

        member this.Text =
            match this with
            | Small -> "Small"
            | Large -> "Large"

let name = Var.create ""
let quantity = Var.create "1"
let size: Var<SelectField.OptionItem option> = Var.create None
let gift: Var<RadioField.OptionItem option> = Var.create None
let notes = Var.create ""
let agreed = Var.create false

let form =
    Form.succeed (fun name quantity size gift notes agreed ->
        $"%d{quantity} x %s{name} (%A{size}), gift: %A{gift}, notes: %s{notes}, agreed: %b{agreed}"
    )
    |> Form.append (
        TextField.create "name"
        |> TextField.withLabel "Product"
        |> TextField.withPlaceholder "What are you ordering?"
        |> Field.create name Ok
        |> Form.textField
    )
    |> Form.append (
        NumberField.create "quantity"
        |> NumberField.withLabel "Quantity"
        |> NumberField.withMin 1.0
        |> Field.create
            quantity
            (fun value ->
                match System.Int32.TryParse value with
                | true, n when n > 0 -> Ok n
                | _ -> Error "A whole number, at least 1"
            )
        |> Form.numberField
    )
    |> Form.append (
        SelectField.create "size"
        |> SelectField.withLabel "Size"
        |> SelectField.withPlaceholder "Choose"
        |> SelectField.withOptions
            [
                Size.Small
                Size.Large
            ]
        |> Field.create
            size
            (fun value ->
                match value with
                | Some item -> Ok(item :?> Size)
                | None -> Error "Choose a size"
            )
        |> Form.selectField
    )
    |> Form.append (
        RadioField.create "gift"
        |> RadioField.withLabel "Gift wrap"
        |> RadioField.withBasicOptions
            [
                "yes", "Yes"
                "no", "No"
            ]
        |> Field.create
            gift
            (fun value ->
                match value with
                | Some item -> Ok(item.Key = "yes")
                | None -> Error "Yes or no"
            )
        |> Form.radioField
    )
    |> Form.append (
        TextareaField.create "notes"
        |> TextareaField.withLabel "Notes"
        |> TextareaField.withRows 2
        |> Field.create notes Ok
        |> Form.textareaField
        |> Form.optional
        |> Form.map (Option.defaultValue "none")
    )
    |> Form.append (
        CheckboxField.create "agreed"
        |> CheckboxField.withText "I agree to the terms"
        |> Field.create
            agreed
            (fun value ->
                if value then
                    Ok value
                else
                    Error "You have to agree"
            )
        |> Form.checkboxField
    )

let state = Var.create View.Idle

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

The kinds

ConstructorAttributesThe Var holds
Form.textFieldTextFieldstring
Form.emailFieldEmailFieldstring
Form.passwordFieldPasswordFieldstring
Form.numberFieldNumberFieldstring
Form.textareaFieldTextareaFieldstring
Form.checkboxFieldCheckboxFieldbool
Form.selectFieldSelectFieldSelectField.OptionItem option
Form.radioFieldRadioFieldRadioField.OptionItem option

Text-like fields hold a string even for numbers: the parser is where "42" becomes 42, and where a bad entry becomes a message. The builders on each attributes type (withLabel, withPlaceholder, withMin, withRows, ...) are listed in the API reference.

A kind that is not in the table is a custom field, and a kind with other markup is a customized view.

Choices

A select or a radio group holds an OptionItem option. An option is anything with a Key and a Text; a union that implements the interface, as Size above, gives you typed options. The parser casts the chosen item back: item :?> Size.

For a quick list of strings, withBasicOptions takes key and text pairs, and the parser reads item.Key.

Empty and optional

An empty field, an empty string or None, never reaches the parser: it is an error of its own, shown as "This field is required". A field that may stay empty is wrapped in Form.optional: it then yields None while empty and Some value once filled, with the parser applied only to the filled case. The notes field above does that, and Form.map turns the None into a default.

A checkbox is never empty; a checkbox that must be ticked says so in its parser, as the last field does.

Labels that change

Form.withLabel replaces a field's label with a tracked read, so the label can follow other values. Lists use it to number their items.

Disabled and read-only

Form.disable, Form.disableIf, Form.readOnly and Form.readOnlyIf apply to a field or to a whole sub-form. The If variants take a condition that is a tracked read, so Form.disableIf (fun () -> locked.Value) follows the locked Var.

Edit this page