formz/formz_builder

This module is used to construct a form using the builder pattern. A form is a list of fields and a decoder function.

Examples

decodes(fn(field) { field })
|> require(field("name"), defintions.text_field())
|> data([#("name", "Louis"))])
|> parse
# -> Ok("Louis")
new()
|> optional(field("greeting"), defintions.text_field())
|> optional(field("name"), defintions.text_field())
|> data([#("greeting", "Hello"), #("name", "World")])
|> set_decoder(fn(greeting) { fn(name) { greeting <> " " <> name } })
|> parse
# -> Ok("Hello World")

Types

pub opaque type Form(widget, output, decoder, has_decoder)

A phantom type used to tag a form as having a decoder.

pub type HasDecoder

A phantom type used to tag a form as not having a decoder.

pub type NoDecoder

Functions

pub fn data(
  form: Form(a, b, c, d),
  input_data: List(#(String, String)),
) -> Form(a, b, c, d)

Add input data to this form. This will set the raw string value of the fields. It does not trigger any parsing, so you can also use this to set default values (if you do it in your form generator function) or initial values (if you do it before rendering an empty form).

pub fn decodes(decoder: a) -> Form(b, a, a, HasDecoder)

Create a new empty form with no fields that uses the provided decoder when parsing to provide the final value of a valid form.

A decoder is required in order to parse a form, though you can use new() to create a form without one, to be added later.

The type signature of the decoder must match the types of the definitions of the fields added to the form. If one field has been added, then the decoder needs to be a function that returns a value. If 15 fields have been added, then the decoder needs to be a function that returns a function that returns a function and so on, until 15 functions have been called to return the final value.

Example

decodes(1)
|> parse
# -> Ok(1)
decodes(fn(field) { field })
|> require(field("name"), defintions.text_field())
|> data([#("name", "Louis"))])
|> parse
# -> Ok("Louis")
decodes(fn(greeting) { fn(name) { greeting <> " " <> name } })
|> optional(field("greeting"), defintions.text_field())
|> optional(field("name"), defintions.text_field())
|> data([#("greeting", "Hello"), #("name", "World")])
|> parse
# -> Ok("Hello World")
pub fn get(
  form: Form(a, b, c, d),
  name: String,
) -> Result(FormItem(a), Nil)

Get the FormItem with the given name. If multiple items have the same name, the first one is returned.

pub fn items(form: Form(a, b, c, d)) -> List(FormItem(a))

Get each FormItem added to the form. Any time a field or subform are added, a FormItem is created.

pub fn new() -> Form(a, b, b, NoDecoder)

Create a new empty form with no fields that a decoder will have to be added to in order to parse it.

Example

new()
|> set_decoder(1)
|> parse
# -> Ok(1)
pub fn optional(
  previous_form: Form(a, fn(b) -> c, d, e),
  field: Field,
  definition: Definition(a, f, b),
) -> Form(a, c, d, e)

Add an optional field to a form.

This will use both the parse and optional_parse functions from the definition to parse the input data when parsing this field.

pub fn parse(
  form: Form(a, b, c, HasDecoder),
) -> Result(b, Form(a, b, c, HasDecoder))

Parse the form. This means step through the fields one by one, parsing them individually. If any field fails to parse, the whole form is considered invalid, however it will still continue parsing the rest of the fields to collect all errors. This is useful for showing all errors at once. If no fields fail to parse, the decoded value is returned, which is the value given to create_form.

If you’d like to parse the form but not get the output, so you can give feedback to a user in response to input, you can use validate or validate_all.

pub fn parse_then_try(
  form: Form(a, b, c, HasDecoder),
  apply fun: fn(Form(a, b, c, HasDecoder), b) ->
    Result(d, Form(a, b, c, HasDecoder)),
) -> Result(d, Form(a, b, c, HasDecoder))

Parse the form, then apply a function to the output if it was successful. This is a very thin wrapper around parse and result.try, but the difference being it will pass the form along to the function as the successful result. This allows you to easily update the form fields with errors or other information based on the output.

This is useful for situations where you can have errors in the form that aren’t easily checked in simple parsing functions. Like, say, hitting a db to check if a username is taken.

make_form()
|> data(form_data)
|> parse_then_try(fn(username, form) {
  case is_username_taken(username) {
    Ok(false) -> Ok(form)
    Ok(true) -> update_field(form, "username", field.set_error(_, "Username is taken"))
  }
}
pub fn require(
  previous_form: Form(a, fn(b) -> c, d, e),
  field: Field,
  definition: Definition(a, b, f),
) -> Form(a, c, d, e)

Add a required field to a form.

This will use only the parse function from the definition to parse the input data when parsing this field. Ultimately whether a field is actually required or not comes down to the details of the definition.

This will also set the required value on the field to True. Form generators can use this to mark the HTML input elements as required for accessibility.

pub fn set_decoder(
  form: Form(a, b, c, d),
  decoder: c,
) -> Form(a, b, c, HasDecoder)

Replace or set the decoder for the form. A decoder is required in order to parse a form. The type signature of the decoder must match the types of the definitions of the fields added to the form. If one field has been added, then the decoder needs to be a function that returns a value. If 15 fields have been added, then the decoder needs to be a function that returns a function that returns a function and so on, until 15 functions have been called to return the final value.

pub fn subform(
  previous_form: Form(a, fn(b) -> c, d, e),
  details: SubForm,
  sub: Form(a, b, f, HasDecoder),
) -> Form(a, c, d, e)

Add a form as a subform. This will essentially append the fields from the subform to the current form, prefixing their names with the name of the subform. Form generators will still see the fields as a set though, so they can be marked up as a group for accessibility reasons.

pub fn update(
  form: Form(a, b, c, d),
  name: String,
  fun: fn(FormItem(a)) -> FormItem(a),
) -> Form(a, b, c, e)

Update the FormItem with the given name using the provided function. If multiple items have the same name, it will be called on all of them.

pub fn update_field(
  form: Form(a, b, c, d),
  name: String,
  fun: fn(Field) -> Field,
) -> Form(a, b, c, d)

Update the Field with the given name using the provided function. If multiple fields have the same name, it will be called on all of them.

let form = make_form()
update(form, "name", field.set_label(_, "Full Name"))
pub fn update_subform(
  form: Form(a, b, c, d),
  name: String,
  fun: fn(SubForm) -> SubForm,
) -> Form(a, b, c, d)

Update the SubForm with the given name using the provided function. If multiple subforms have the same name, it will be called on all of them.

let form = make_form()
update(form, "name", subform.set_help_text(_, "..."))
pub fn validate(
  form: Form(a, b, c, HasDecoder),
  names: List(String),
) -> Form(a, b, c, HasDecoder)

Validate specific fields of the form. This is similar to parse, but instead of returning the decoded output if there are no errors, it returns the valid form. This is useful for if you want to be able to give feedback to the user about whether certain fields are valid or not. In this case you could just validate only fields that the user has interacted with.

pub fn validate_all(
  form: Form(a, b, c, HasDecoder),
) -> Form(a, b, c, HasDecoder)

Validate all the fields in the form. This is similar to parse, but instead of returning the decoded output if there are no errors, it returns the valid form. This is useful for if you want to be able to give feedback to the user about whether certain fields are valid or not.

Search Document