···37373838pub fn make_form() {
3939 formz.new()
4040- |> formz.add(field("username"), definitions.text_field())
4141- |> formz.add(field("password"), definitions.password_field())
4040+ |> formz.require(field("username"), definitions.text_field())
4141+ |> formz.require(field("password"), definitions.password_field())
4242 |> formz.decodes(fn(username) { fn(password) { #(username, password) } })
4343}
4444```
···5454import formz_string/definitions
55555656pub fn make_form() {
5757- use username <- formz.with(field("username"), definitions.text_field())
5858- use password <- formz.with(field("password"), definitions.password_field())
5757+ use username <- formz.require(field("username"), definitions.text_field())
5858+ use password <- formz.require(field("password"), definitions.password_field())
59596060 formz.create_form(#(username, password))
6161}
···63636464## Creating fields
65656666-There are two parts to adding a field to a form (seen above):
6666+There are two arguments to adding a field to a form (seen above):
67676868-1. Specific, unique details about the field, such as its name, label, help text,
6969- disabled/enabled state, etc.
7070-2. A field "definition" which says (A) how to generate the HTML "widget"
7171- for the field, and (B) how to parse, or "transform" the data from the field. These
7272- definitions are reusable and can be shared between fields, forms and projects.
6868+1. Specific, unique [details](https://hexdocs.pm/formz/formz/field.html) about
6969+ the field, such as its name, label, help text, disabled state, etc.
7070+2. A field [definition](https://hexdocs.pm/formz/formz/definition.html) which
7171+ says (A) how to generate the HTML *widget* for the field, and (B) how to
7272+ parse the data from the field. These definitions are reusable and can be
7373+ shared across fields, forms and projects.
73747475### Field details
7576···8182```
82838384```gleam
8484-field(named: "userid") |> field.make_hidden |> field.set_value("42")
8585+field(named: "userid") |> field.make_hidden |> field.set_raw_value("42")
8586```
86878788### Field definition
88899090+[Defintions](https://hexdocs.pm/formz/formz/definition.html) are the heavy
9191+compared to the lightness of fields; they take a bit more work to make as they
9292+are intended to be more reusable.
9393+9494+The first role of a defintion is to generate the HTML widget for the field.
8995This library is format-agnostic and you can generate HTML widgets as raw
9090-strings, Lustre elements, Nakai nodes, something else, etc. There are
9696+strings, Lustre elements, Nakai nodes, something else, etc, etc. There are
9197currently three formz libraries that provide common field definitions in
9298different formats.
9399···96102- [formz_lustre](https://hexdocs.pm/formz_lustre/) (untested in a browser,
97103 would it be useful there??)
98104105105+The second role is to parse the data from the field. There are a two parts
106106+to this, as how you parse a field's value depends on if it is optional or
107107+required. For example, an optional text field might be an empty string,
108108+an optional checkbox might be `false`, and an optional select might
109109+be `option.None`. So you need to provide two parse functions, one for when
110110+a field is required, and a second for when it's optional (and it uses the first
111111+one).
99112100100-There is also a *simple* validation module with some examples, and to cover
101101-some basics.
113113+There is also a basic validation module with the simple parsers required to make
114114+the basic form definitions provided above work. You can use this as a starting
115115+point for your own parse functions.
102116103117```gleam
104118/// you won't often need to do this directly (I think??). The idea is that
···125139}
126140127141pub fn password_field() {
128128- Definition(password_widget, validation.string, "")
142142+ Definition(
143143+ widget: password_widget,
144144+ parse: validation.string,
145145+ optional_parse: fn(parse, str) {
146146+ case str {
147147+ "" -> Ok(option.None)
148148+ _ -> parse(str)
149149+ }
150150+ },
151151+ // We need to have a stub value for each parser that's used
152152+ // when building the decoder and parse functions for the form as the fields
153153+ // are being added
154154+ stub: "",
155155+ optional_stub: option.None,
156156+ )
129157}
130158```
131159···141169The specifics of how you would do this are going
142170to vary greatly for each project and its styling/markup needs.
143171144144-145172However, the three `formz_*` libraries mentioned above all provide a
146173simple form generator function that you can use as is, or as a starting
147174point for your own. `formz` is BYOS, Bring Your Own Stylesheet, so the
···149176a super simple CSS file to get the ball rolling and make the default
150177forms easier to use out of the box.
151178152152-That said, you can create the form HTML yourself, directly for each field.
153153-There's an example in the demo project showing how to do this.
179179+That said, you can also create the form HTML yourself, directly for each field.
180180+There's [an example](https://github.com/bentomas/formz/blob/main/formz_demo/src/formz_demo/examples/custom_output.gleam)
181181+in the demo project showing how to do this.
154182155183### Generating form HTML using the `formz_string` library
156184157157-The built-in form generators all leave it as homework to add the form tags
158158-and submit buttons.
185185+The built-in form generators leave it as homework to add the form tags and
186186+submit buttons.
159187160188```gleam
161189import formz_string/simple
···214242 |> Error
215243 _ ->
216244 form
217217- |> formz.update_field("username", field.set_error(_, "Wrong username"))
245245+ |> formz.update_field("username", field.set_error(_, "Unknown username"))
218246 |> Error
219247 }
220248 })
+2-2
formz/TODO.md
···33- csrf token?
44- emit warning on duplicate named fields?
55- clean names so snake_case?
66-- disabled fields
77-- optional fields? (can do optional validation, but that isn't reflected in the html)
86- errors on hidden fields?
97- custom types for input.hidden, input.disabled, input.required?
108- date fields? https://hexdocs.pm/birl/ https://hexdocs.pm/rada/
99+- provide password/email/number_field? or just make them change the widget?
1010+- provide a text_area field? or make them change the widget?
···55import formz_string/widgets
6677pub fn make_form() {
88- use a <- formz.with(field("a"), definitions.text_field())
99- use b <- formz.with(field("b"), definitions.integer_field())
1010- use c <- formz.with(field("c"), definitions.number_field())
1111- use d <- formz.with(field("d"), definitions.boolean_field())
1212- use e <- formz.with(field("e"), definitions.email_field())
1313- use f <- formz.with(field("f"), definitions.password_field())
1414- use g <- formz.with(
1515- field("g"),
1616- definitions.choices_field(letters(), placeholder: A),
88+ use a <- formz.optional(field("text"), definitions.text_field())
99+ use b <- formz.optional(field("int"), definitions.integer_field())
1010+ use c <- formz.optional(field("number"), definitions.number_field())
1111+ use d <- formz.optional(field("bool"), definitions.boolean_field())
1212+ use e <- formz.optional(field("email"), definitions.email_field())
1313+ use f <- formz.optional(field("password"), definitions.password_field())
1414+ use g <- formz.optional(
1515+ field("choices"),
1616+ definitions.choices_field(letters(), A),
1717+ )
1818+ use h <- formz.optional(
1919+ field("list"),
2020+ definitions.list_field(["Dog", "Cat", "Ant"]),
1721 )
1818- use h <- formz.with(field("h"), definitions.list_field(["Dog", "Cat", "Ant"]))
1919- use i <- formz.with(
2020- field("i"),
2222+ use i <- formz.optional(
2323+ field("textarea_widget"),
2124 definitions.text_field()
2225 |> definition.set_widget(widgets.textarea_widget()),
2326 )
···33import formz_string/definitions
4455pub fn make_form() {
66- use name <- formz.with(field("name"), definitions.text_field())
66+ use name <- formz.require(field("name"), definitions.text_field())
77 formz.create_form("Hello " <> name)
88}
+3-3
formz_demo/src/formz_demo/examples/labels.gleam
···33import formz_string/definitions
4455pub fn make_form() {
66- use name <- formz.with(field(named: "name"), is: definitions.text_field())
77- use age <- formz.with(
66+ use name <- formz.require(field(named: "name"), is: definitions.text_field())
77+ use age <- formz.require(
88 field("age") |> field.set_label("Age"),
99 is: definitions.integer_field(),
1010 )
1111- use height <- formz.with(
1111+ use height <- formz.require(
1212 field("height")
1313 |> field.set_label("Height (cm)")
1414 |> field.set_help_text("Please enter your height in centimeters"),
+2-2
formz_demo/src/formz_demo/examples/login.gleam
···1212}
13131414pub fn make_form() {
1515- use username <- formz.with(field("username"), definitions.text_field())
1616- use password <- formz.with(field("password"), definitions.password_field())
1515+ use username <- formz.require(field("username"), definitions.text_field())
1616+ use password <- formz.require(field("password"), definitions.password_field())
17171818 formz.create_form(Credentials(username, password))
1919}
···11+---
22+version: 1.2.3
33+title: text like labelled by element with id
44+file: ./test/formz_string/widgets_test.gleam
55+test_name: hello_birdie_test
66+---
77+<input type="text" name="name" value="hello" aria-labelledby="id">
···11+---
22+version: 1.2.3
33+title: text like labelled by field value
44+file: ./test/formz_string/widgets_test.gleam
55+test_name: hello_birdie_test
66+---
77+<input type="text" name="name" value="hello" aria-label="Label">