Fields
Every field is built the same way, whatever its kind.
Start from the attributes
createtakes the field's id; thewith*builders say how it looks. Each kind has its own type:TextField,NumberField,SelectField, and so on.TextField.create "product" |> TextField.withLabel "Product"Add the value and the parser
Field.createties the attributes to aVarand a parser. The parser turns what the user typed into your type, or into a message to show. PassOkwhen the text is the value.|> Field.create name (fun value -> if value = "" then Error "Required" else Ok value)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
| Constructor | Attributes | The Var holds |
|---|---|---|
Form.textField | TextField | string |
Form.emailField | EmailField | string |
Form.passwordField | PasswordField | string |
Form.numberField | NumberField | string |
Form.textareaField | TextareaField | string |
Form.checkboxField | CheckboxField | bool |
Form.selectField | SelectField | SelectField.OptionItem option |
Form.radioField | RadioField | RadioField.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.