···33[](https://hex.pm/packages/formz)
44[](https://hexdocs.pm/formz/)
5566+A Gleam library for parsing and generating accessible HTML forms.
77+88+> **Note:** This library currently has two non-interoperable ways to define forms,
99+one using the builder pattern, and one using a series of `use` calls like with
1010+the [toy](https://hexdocs.pm/toy/) or [decode/zero](https://hexdocs.pm/decode/)
1111+packages. After gathering some feedback, only one of them will be kept.
1212+1313+1414+HTML forms rendered in the browser and the data they are parsed into are
1515+intrinsically linked. Treating the markup and the parsing as two separate
1616+problems to solve is inconvenient and leads to bugs. This library aims
1717+to make that link explicit and easy to manage, while making it really easy
1818+to make accessible forms.
1919+620```sh
77-gleam add formz@1
2121+gleam add formz@0.1
2222+```
2323+2424+## Creating a form
2525+2626+A `formz` form is a list of fields and a decoder function.
2727+2828+### builder pattern
2929+3030+With the builder pattern, you add the fields and then explicitly specify the
3131+decoder function...
3232+3333+```gleam
3434+import formz/field.{field}
3535+import formz/formz_builder as formz
3636+import formz_string/definitions
3737+3838+pub fn make_form() {
3939+ formz.new()
4040+ |> formz.add(field("username"), definitions.text_field())
4141+ |> formz.add(field("password"), definitions.password_field())
4242+ |> formz.decodes(fn(username) { fn(password) { #(username, password) } })
4343+}
4444+```
4545+4646+### `use`/callbacks pattern
4747+4848+With the `use`/callbakcks pattern, you create the decoder function as you add
4949+the fields...
5050+5151+```gleam
5252+import formz/field.{field}
5353+import formz/formz_use as formz
5454+import formz_string/definitions
5555+5656+pub fn make_form() {
5757+ use username <- formz.with(field("username"), definitions.text_field())
5858+ use password <- formz.with(field("password"), definitions.password_field())
5959+6060+ formz.create_form(#(username, password))
6161+}
6262+```
6363+6464+## Creating fields
6565+6666+There are two parts to adding a field to a form (seen above):
6767+6868+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.
7373+7474+### Field details
7575+7676+```gleam
7777+// name is required, the other details are optional
7878+field(named: "username")
7979+|> field.set_label("Username")
8080+|> field.set_help_text("Only alphanumeric characters are allowed.")
8181+```
8282+8383+```gleam
8484+field(named: "userid") |> field.make_hidden |> field.set_value("42")
8585+```
8686+8787+### Field definition
8888+8989+This library is format-agnostic and you can generate HTML widgets as raw
9090+strings, Lustre elements, Nakai nodes, something else, etc. There are
9191+currently three formz libraries that provide common field definitions in
9292+different formats.
9393+9494+- [formz_string](https://hexdocs.pm/formz_string/)
9595+- [formz_nakai](https://hexdocs.pm/formz_nakai/)
9696+- [formz_lustre](https://hexdocs.pm/formz_lustre/) (untested in a browser,
9797+ would it be useful there??)
9898+9999+100100+There is also a *simple* validation module with some examples, and to cover
101101+some basics.
102102+103103+```gleam
104104+/// you won't often need to do this directly (I think??). The idea is that
105105+/// there'd be libs with the definitions you need.
106106+107107+import formz/definition.{Definition}
108108+import formz/field
109109+import formz/validation
110110+import formz/widget
111111+import lustre/attribute
112112+import lustre/element
113113+import lustre/element/html
114114+115115+fn password_widget(
116116+ field: field.Field,
117117+ args: widget.Args,
118118+) -> element.Element(msg) {
119119+ html.input([
120120+ attribute.type_("password"),
121121+ attribute.name(field.name),
122122+ attribute.id(args.id),
123123+ attribute.attribute("aria-labelledby", field.label),
124124+ ])
125125+}
126126+127127+pub fn password_field() {
128128+ Definition(password_widget, validation.string, "")
129129+}
8130```
131131+132132+133133+134134+## Generating HTML for a form
135135+136136+Generally speaking, the idea with a `formz` form is that you are not going
137137+to generate the HTML for each field individually, but rather, you'd use
138138+a function to loop through each field, generating semantic, accessible
139139+markup for each one.
140140+141141+The specifics of how you would do this are going
142142+to vary greatly for each project and its styling/markup needs.
143143+144144+145145+However, the three `formz_*` libraries mentioned above all provide a
146146+simple form generator function that you can use as is, or as a starting
147147+point for your own. `formz` is BYOS, Bring Your Own Stylesheet, so the
148148+built-in form generators come unstyled. If there is interest, I could add
149149+a super simple CSS file to get the ball rolling and make the default
150150+forms easier to use out of the box.
151151+152152+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.
154154+155155+### Generating form HTML using the `formz_string` library
156156+157157+The built-in form generators all leave it as homework to add the form tags
158158+and submit buttons.
159159+9160```gleam
1010-import formz
161161+import formz_string/simple
162162+163163+pub fn show_form(form) -> String {
164164+ "<form method=\"post\">"
165165+ <> simple.generate_form(form)
166166+ <> "<p><button type\"submit\">Submit</button></p>"
167167+ <> "</form>"
168168+}
169169+```
170170+171171+172172+## Parsing form data
173173+174174+You can parse a `formz` form with a tuple of values and names, typically from
175175+a POST request. Here we parse in a `wisp` handler:
176176+177177+```gleam
178178+pub fn handle_form_submission(req: Request) -> Response {
179179+ use formdata <- wisp.require_form(req)
180180+181181+ let result = make_form()
182182+ |> formz.data(formdata.values)
183183+ |> formz.parse
111841212-pub fn main() {
1313- // TODO: An example of the project in use
185185+ case result {
186186+ Ok(credentials) -> {
187187+ let #(username, password) = credentials
188188+ wisp.ok()
189189+ |> wisp.html_body(string_builder.from_string("Hello "<>username<>"!"))
190190+ }
191191+ Error(form_with_errors) -> {
192192+ show_form(form_with_errors)
193193+ }
194194+ }
14195}
15196```
161971717-Further documentation can be found at <https://hexdocs.pm/formz>.
198198+However, often you want to parse a form, and then... you know... act on that
199199+data, and in doing so you might discover more errors for the form. In this
200200+situation you can use `parse_then_try`:
182011919-## Development
202202+```gleam
203203+pub fn handle_form_submission(req: Request) -> Response {
204204+ use formdata <- wisp.require_form(req)
205205+206206+ let result = make_form()
207207+ |> formz.data(formdata.values)
208208+ |> formz.parse_then_try(fn(form, credentials) {
209209+ case credentials {
210210+ #("admin" as username, "l33t") -> Ok(username)
211211+ #("admin", _) ->
212212+ form
213213+ |> formz.update_field("password", field.set_error(_, "Wrong password"))
214214+ |> Error
215215+ _ ->
216216+ form
217217+ |> formz.update_field("username", field.set_error(_, "Wrong username"))
218218+ |> Error
219219+ }
220220+ })
202212121-```sh
2222-gleam run # Run the project
2323-gleam test # Run the tests
222222+ case result {
223223+ Ok(username) -> {
224224+ wisp.ok()
225225+ |> wisp.html_body(string_builder.from_string("Hello " <> username <> "!"))
226226+ }
227227+ Error(form_with_errors) -> {
228228+ show_form(form_with_errors)
229229+ }
230230+ }
231231+}
24232```
233233+234234+## See it in action
235235+236236+There is a demo wisp app showing a few interactive examples of how `formz` works
237237+[in the repo]().
+1-1
formz/gleam.toml
···11name = "formz"
22-version = "1.0.0"
22+version = "0.1.0"
3344# Fill out these fields if you intend to generate HTML documentation or publish
55# your project to the Hex package manager.
+2
formz/src/formz.gleam
···11+//// This will eventually be the home of `formz_builder` or `formz_use`.
22+13import gleam/io
2435pub fn main() {
···1212 use e <- formz.with(field("e"), definitions.email_field())
1313 use f <- formz.with(field("g"), definitions.enum_field(letters()))
1414 use g <- formz.with(field("h"), definitions.indexed_enum_field(choices))
1515- use h <- formz.with(
1616- field("i"),
1717- definitions.list_field(["Dog", "Cat", "Bird"]),
1818- )
1515+ use h <- formz.with(field("i"), definitions.list_field(["Dog", "Cat", "Ant"]))
19162017 formz.create_form(#(a, b, c, d, e, f, g, h))
2118}