···1111to make that link explicit and easy to manage, while making it really easy
1212to make accessible forms.
13131414-Note: This library is not particularly well-suited for generating one-off
1515-forms, but is more intended for use in projects where you have a few forms to
1414+Note: This library is not necessarily well-suited for generating one-off
1515+forms, and is intended for use in projects where you have a few forms to
1616manage, and would like to keep the form markup and parsing logic in sync. It
1717takes some amount of effort to make an actual form generator with markup and
1818-styles, and that might not be worth it for a one-off form. That said, a simple
1818+styles, and that might not be worth it for a one-off form. That said, a simple
1919form generator is provided if you aren't opinionated about your markup.
20202121```sh
···29293030```gleam
3131import formz
3232-import formz/field.{field}
3332import formz_string/definitions
34333534pub fn make_form() {
3636- use username <- formz.field(
3737- formz.named("username"),
3838- formz.required(definitions.text_field()),
3939- )
4040- use password <- formz.field(
4141- formz.named("password"),
4242- formz.required(definitions.password_field()),
4343- )
3535+ use username <- formz.field(formz.named("username"), definitions.text_field())
3636+ use password <- formz.field(formz.named("password"), definitions.password_field())
44374538 formz.create_form(#(username, password))
4639}
···50435144There are two arguments to adding a field to a form (seen above):
52455353-1. A [Field](https://hexdocs.pm/formz/formz/field.html), which holds specific,
5454- unique details about the field, such as its name, label, help text, disabled
5555- state, etc.
5656-2. A [Definition](https://hexdocs.pm/formz/formz/definition.html), which
5757- says (A) how to generate the HTML input element for the field, and (B) how
5858- to parse the data from the field. These definitions are reusable and can be
5959- shared across fields, forms and projects.
4646+1. A `Config`, which holds specific, unique details about the field: its name,
4747+ label, help text, and disabled state.
4848+2. A `Definition`, which says (A) how to generate the HTML input element for
4949+ the field, and (B) how to parse the data from the field. These definitions
5050+ are reusable and can be shared across fields, forms and projects.
60516161-### Field details
5252+### Field config
62536354```gleam
6464-// name is required, the other details are optional
6565-field(named: "username")
6666-|> field.set_label("Username")
6767-|> field.set_help_text("Only alphanumeric characters are allowed.")
5555+// name is required, the other confg are optional
5656+formz.named("username")
5757+|> formz.set_label("Username")
5858+|> formz.set_help_text("Only alphanumeric characters are allowed.")
6859```
69607061```gleam
7171-field(named: "userid") |> field.make_hidden |> field.set_raw_value("42")
6262+formz.named("userid") |> formz.make_disabled
7263```
73647465### Field definition
75667676-[Defintions](https://hexdocs.pm/formz/formz/definition.html) are the heavy
7777-compared to the lightness of fields; they take a bit more work to make as they
7878-are intended to be more reusable.
6767+A `Definition` describes how an input works, e.g. how it looks and how it's
6868+parsed. Definitions are intended to be reusable.
6969+7070+The first role of a `Defintion` is to generate the HTML input for the field.
7171+This library is format-agnostic and you can generate inputs as raw
7272+strings, Lustre elements, Nakai nodes, something else, etc. The second role
7373+of a `Definition` is to parse the raw string data from the input into a
7474+Gleam type.
79758080-The first role of a `Defintion` is to generate the HTML widget for the field.
8181-This library is format-agnostic and you can generate HTML widgets as raw
8282-strings, Lustre elements, Nakai nodes, something else, etc. There are
8383-currently three `formz` libraries that provide common field definitions for the
8484-most common HTML formats.
7676+There are currently three `formz` libraries that provide common field
7777+definitions for the most common HTML inputs:
85788679- [formz_string](https://hexdocs.pm/formz_string/)
8780- [formz_nakai](https://hexdocs.pm/formz_nakai/)
8888-- [formz_lustre](https://hexdocs.pm/formz_lustre/) (untested in a browser, I've only done this server side)
8989-9090-The second role of a `Definition` is to parse the data from the field. There
9191-are a two parts to this, as how you parse a field's value depends on if it is
9292-optional or required. Not all scenarios can be cookie-cutter placed into an
9393-`Option`. So you need to provide two parse functions, one for when a field is
9494-required, and a second for when it's optional.
8181+- [formz_lustre](https://hexdocs.pm/formz_lustre/)
95829683```gleam
9784/// you won't often need to do this directly (I think??). The idea is that
9885/// there'd be libs with the definitions you need.
9986100100-import formz/definition.{Definition}
101101-import formz/field
8787+import formz
10288import formz/validation
103103-import formz/widget
10489import lustre/attribute
10590import lustre/element
10691import lustre/element/html
1079210893fn password_widget(
109109- field: field.Field,
110110- args: widget.Args,
9494+ config: formz.Config,
9595+ state: formz.InputState,
11196) -> element.Element(msg) {
11297 html.input([
11398 attribute.type_("password"),
114114- attribute.name(field.name),
115115- attribute.id(args.id),
116116- attribute.attribute("aria-labelledby", field.label),
9999+ attribute.name(config.name),
100100+ attribute.id(config.name),
101101+ attribute.attribute("aria-labelledby", config.label),
117102 ])
118103}
119104120105pub fn password_field() {
121121- Definition(
106106+ definition(
122107 widget: password_widget,
123108 parse: validation.string,
124124- optional_parse: fn(parse, str) {
125125- case str {
126126- "" -> Ok(option.None)
127127- _ -> parse(str)
128128- }
129129- },
130130- // We need to have a stub value for each parser. The stubs are used when
131131- // building the decoder and parse functions for the form.
109109+ // We need to have a stub value for each definition. The stubs are used when
110110+ // building the decoder functions for the form. This is just any value of
111111+ // the same type that the parse function returns.
132112 stub: "",
133133- optional_stub: option.None,
134113 )
135114}
136115```
···144123a function to loop through each field, generating semantic, accessible
145124markup for each one.
146125147147-The specifics of how you would do this are going
148148-to vary greatly for each project and its styling/markup needs.
126126+The specifics of how you would do this are going to vary greatly for each
127127+project and its styling/markup needs.
149128150129However, the three `formz_*` libraries mentioned above all provide a
151130simple form generator function that you can use as is, or as a starting
···215194 case credentials {
216195 #("admin" as username, "l33t") -> Ok(username)
217196 #("admin", _) ->
218218- Error(form |> formz.set_field_error("password", "Wrong password"))
197197+ Error(form |> formz.field_error("password", "Wrong password"))
219198 _ ->
220220- Error(form |> formz.set_field_error("username", "Unknown username"))
199199+ Error(form |> formz.field_error("username", "Unknown username"))
221200 }
222201 })
223202
+41-62
formz/README.md
···1111to make that link explicit and easy to manage, while making it really easy
1212to make accessible forms.
13131414-Note: This library is not particularly well-suited for generating one-off
1515-forms, but is more intended for use in projects where you have a few forms to
1414+Note: This library is not necessarily well-suited for generating one-off
1515+forms, and is intended for use in projects where you have a few forms to
1616manage, and would like to keep the form markup and parsing logic in sync. It
1717takes some amount of effort to make an actual form generator with markup and
1818-styles, and that might not be worth it for a one-off form. That said, a simple
1818+styles, and that might not be worth it for a one-off form. That said, a simple
1919form generator is provided if you aren't opinionated about your markup.
20202121```sh
···29293030```gleam
3131import formz
3232-import formz/field.{field}
3332import formz_string/definitions
34333534pub fn make_form() {
3636- use username <- formz.field(
3737- formz.named("username"),
3838- formz.required(definitions.text_field()),
3939- )
4040- use password <- formz.field(
4141- formz.named("password"),
4242- formz.required(definitions.password_field()),
4343- )
3535+ use username <- formz.field(formz.named("username"), definitions.text_field())
3636+ use password <- formz.field(formz.named("password"), definitions.password_field())
44374538 formz.create_form(#(username, password))
4639}
···50435144There are two arguments to adding a field to a form (seen above):
52455353-1. A [Field](https://hexdocs.pm/formz/formz/field.html), which holds specific,
5454- unique details about the field, such as its name, label, help text, disabled
5555- state, etc.
5656-2. A [Definition](https://hexdocs.pm/formz/formz/definition.html), which
5757- says (A) how to generate the HTML input element for the field, and (B) how
5858- to parse the data from the field. These definitions are reusable and can be
5959- shared across fields, forms and projects.
4646+1. A `Config`, which holds specific, unique details about the field: its name,
4747+ label, help text, and disabled state.
4848+2. A `Definition`, which says (A) how to generate the HTML input element for
4949+ the field, and (B) how to parse the data from the field. These definitions
5050+ are reusable and can be shared across fields, forms and projects.
60516161-### Field details
5252+### Field config
62536354```gleam
6464-// name is required, the other details are optional
6565-field(named: "username")
6666-|> field.set_label("Username")
6767-|> field.set_help_text("Only alphanumeric characters are allowed.")
5555+// name is required, the other confg are optional
5656+formz.named("username")
5757+|> formz.set_label("Username")
5858+|> formz.set_help_text("Only alphanumeric characters are allowed.")
6859```
69607061```gleam
7171-field(named: "userid") |> field.make_hidden |> field.set_raw_value("42")
6262+formz.named("userid") |> formz.make_disabled
7263```
73647465### Field definition
75667676-[Defintions](https://hexdocs.pm/formz/formz/definition.html) are the heavy
7777-compared to the lightness of fields; they take a bit more work to make as they
7878-are intended to be more reusable.
6767+A `Definition` describes how an input works, e.g. how it looks and how it's
6868+parsed. Definitions are intended to be reusable.
6969+7070+The first role of a `Defintion` is to generate the HTML input for the field.
7171+This library is format-agnostic and you can generate inputs as raw
7272+strings, Lustre elements, Nakai nodes, something else, etc. The second role
7373+of a `Definition` is to parse the raw string data from the input into a
7474+Gleam type.
79758080-The first role of a `Defintion` is to generate the HTML widget for the field.
8181-This library is format-agnostic and you can generate HTML widgets as raw
8282-strings, Lustre elements, Nakai nodes, something else, etc. There are
8383-currently three `formz` libraries that provide common field definitions for the
8484-most common HTML formats.
7676+There are currently three `formz` libraries that provide common field
7777+definitions for the most common HTML inputs:
85788679- [formz_string](https://hexdocs.pm/formz_string/)
8780- [formz_nakai](https://hexdocs.pm/formz_nakai/)
8888-- [formz_lustre](https://hexdocs.pm/formz_lustre/) (untested in a browser, I've only done this server side)
8989-9090-The second role of a `Definition` is to parse the data from the field. There
9191-are a two parts to this, as how you parse a field's value depends on if it is
9292-optional or required. Not all scenarios can be cookie-cutter placed into an
9393-`Option`. So you need to provide two parse functions, one for when a field is
9494-required, and a second for when it's optional.
8181+- [formz_lustre](https://hexdocs.pm/formz_lustre/)
95829683```gleam
9784/// you won't often need to do this directly (I think??). The idea is that
9885/// there'd be libs with the definitions you need.
9986100100-import formz/definition.{Definition}
101101-import formz/field
8787+import formz
10288import formz/validation
103103-import formz/widget
10489import lustre/attribute
10590import lustre/element
10691import lustre/element/html
1079210893fn password_widget(
109109- field: field.Field,
110110- args: widget.Args,
9494+ config: formz.Config,
9595+ state: formz.InputState,
11196) -> element.Element(msg) {
11297 html.input([
11398 attribute.type_("password"),
114114- attribute.name(field.name),
115115- attribute.id(args.id),
116116- attribute.attribute("aria-labelledby", field.label),
9999+ attribute.name(config.name),
100100+ attribute.id(config.name),
101101+ attribute.attribute("aria-labelledby", config.label),
117102 ])
118103}
119104120105pub fn password_field() {
121121- Definition(
106106+ definition(
122107 widget: password_widget,
123108 parse: validation.string,
124124- optional_parse: fn(parse, str) {
125125- case str {
126126- "" -> Ok(option.None)
127127- _ -> parse(str)
128128- }
129129- },
130130- // We need to have a stub value for each parser. The stubs are used when
131131- // building the decoder and parse functions for the form.
109109+ // We need to have a stub value for each definition. The stubs are used when
110110+ // building the decoder functions for the form. This is just any value of
111111+ // the same type that the parse function returns.
132112 stub: "",
133133- optional_stub: option.None,
134113 )
135114}
136115```
···144123a function to loop through each field, generating semantic, accessible
145124markup for each one.
146125147147-The specifics of how you would do this are going
148148-to vary greatly for each project and its styling/markup needs.
126126+The specifics of how you would do this are going to vary greatly for each
127127+project and its styling/markup needs.
149128150129However, the three `formz_*` libraries mentioned above all provide a
151130simple form generator function that you can use as is, or as a starting
···215194 case credentials {
216195 #("admin" as username, "l33t") -> Ok(username)
217196 #("admin", _) ->
218218- Error(form |> formz.set_field_error("password", "Wrong password"))
197197+ Error(form |> formz.field_error("password", "Wrong password"))
219198 _ ->
220220- Error(form |> formz.set_field_error("username", "Unknown username"))
199199+ Error(form |> formz.field_error("username", "Unknown username"))
221200 }
222201 })
223202
+1
formz/TODO.md
···11+- better error messages. include field name?
12- form sets
23- csrf token?
34- multiple value fields?
+174-117
formz/src/formz.gleam
···1111//// <td>Creating a form</td>
1212//// <td>
1313//// <a href="#create_form">create_form</a><br>
1414-//// <a href="#require">require</a><br>
1515-//// <a href="#optional">optional</a><br>
1414+//// <a href="#required_field">required_field</a><br>
1515+//// <a href="#field">field</a><br>
1616//// <a href="#list">list</a><br>
1717//// <a href="#limited_list">limited_list</a><br>
1818//// <a href="#subform">subform</a>
···5151//// <td>
5252//// <a href="#get">get</a><br>
5353//// <a href="#items">items</a><br>
5454-//// <a href="#set_field_error">set_field_error</a><br>
5555-//// <a href="#set_listfield_errors">set_listfield_errors</a><br>
5454+//// <a href="#field_error">field_error</a><br>
5555+//// <a href="#listfield_errors">listfield_errors</a><br>
5656//// <a href="#update">update</a><br>
5757+//// </td>
5858+//// </tr>
5959+//// <tr>
6060+//// <td>Accessing and manipulating config for a form item</td>
6161+//// <td>
5762//// <a href="#update_field">update_field</a><br>
5858-//// <a href="#update_listfield">update_listfield</a><br>
5959-//// <a href="#update_subform">update_subform</a>
6363+//// <a href="#set_name">set_name</a><br>
6464+//// <a href="#set_label">set_label</a><br>
6565+//// <a href="#set_help_text">set_help_text</a><br>
6666+//// <a href="#set_disabled">set_disabled</a><br>
6767+//// <a href="#make_disabled">make_disabled</a><br>
6068//// </td>
6169//// </tr>
6270//// </table>
···6573////
6674//// ```gleam
6775//// fn make_form() {
6868-//// use name <- formz.require(field("name"), definitions.text_field())
7676+//// use name <- formz.required_field(field("name"), definitions.text_field())
6977////
7078//// formz.create_form(name)
7179//// }
···8088////
8189//// ```gleam
8290//// fn make_form() {
8383-//// use greeting <- optional(field("greeting"), definitions.text_field())
8484-//// use name <- optional(field("name"), definitions.text_field())
9191+//// use greeting <- field(field("greeting"), definitions.text_field())
9292+//// use name <- field(field("name"), definitions.text_field())
8593////
8694//// formz.create_form(greeting <> " " <> name)
8795//// }
···94102//// }
95103//// ```
961049797-import formz/field
9898-import formz/subform
99105import gleam/dict.{type Dict}
100106import gleam/int
101107import gleam/list
102108import gleam/option.{None, Some}
103109import gleam/result
104110import gleam/string
111111+import justin
105112106113/// You create this using the `create_form` function.
107114///
···119126 )
120127}
121128122122-/// You add an `Item` to a form using the `optional`, `require`, `list`,
129129+/// You add an `Item` to a form using the `field`, `required_field`, `list`,
123130/// `limited_list` and `subform` functions. A form is a list of `Item`s and
124124-/// each item is parsed to a single value, which the decode function will
125125-/// choose how to use.
131131+/// each item is parsed to a single value, and then passed to the decode
132132+/// function.
126133///
127134/// You primarily only use an `Item` directly when writing a form generator
128128-/// function to output your function to HTML.
135135+/// function to output your form to HTML.
129136///
130137/// You can also manipulate an `Item` after a form has been created, to change
131131-/// things like labels, help text, etc. There are specific functions,
132132-/// `update_field`, `update_listfield` and `update_subform`, to help with this,
133133-/// so you don't have to pattern match when updating a specific item.
138138+/// things like labels, help text, etc with `update`. Each item has a `Config`
139139+/// that describes how it works, and this can be updated directly
140140+/// with `update_config`.
134141///
135142/// ```
136143/// let form = make_form()
137137-/// form.update_field("name", field.set_label(_, "Full Name"))
144144+/// formz.update_config("name", formz.set_label(_, "Full Name"))
138145/// ```
139146pub type Item(widget) {
140147 /// A single field that (generally speaking) corresponds to a single
141148 /// HTML input
142142- Field(detail: field.Field, state: InputState, widget: widget)
149149+ Field(config: Config, state: InputState, widget: widget)
143150 /// A single field that a consumer can submit multiple values for.
144151 ListField(
145145- detail: field.Field,
152152+ config: Config,
146153 states: List(InputState),
147154 limit_check: LimitCheck,
148155 widget: widget,
149156 )
150157 /// A group of fields that are added as and then parsed to a single unit.
151151- SubForm(detail: subform.SubForm, items: List(Item(widget)))
158158+ SubForm(config: Config, items: List(Item(widget)))
152159}
153160154161/// The state of the an input for a field. This is used to track the current
···166173 Required
167174}
168175169169-/// A `Definition` describes how a field works, e.g. how it looks and how it's
170170-/// parsed. It is the heavy compared to the lightness of a
171171-/// [Field](https://hexdocs.pm/formz/formz/field.html);
172172-/// definitions take a bit more work to make as they are intended to be reusable.
176176+/// Configuration for the form `Item`. The only required information here is
177177+/// `name` and there is a `named` function to make a config with a name. You
178178+/// can then chain the `set_x` functions to add the other information as needed.
179179+///
180180+/// ### Example
181181+///
182182+/// ```
183183+/// let config = formz.named("name") |> formz.set_label("Full Name")
184184+/// ```
185185+///
186186+/// ```
187187+/// let config =
188188+/// formz.named("name")
189189+/// |> formz.set_label("Full Name")
190190+/// |> formz.set_help_text("Enter your full name")
191191+/// ```
192192+///
193193+/// ```
194194+/// let config = formz.named("name") |> formz.make_disabled
195195+/// ```
196196+pub type Config {
197197+ Config(
198198+ /// The name of the field or subform. The only truly required information
199199+ /// for a form `Item`. This is used to identify the field in the form.
200200+ /// It should be unique for each form, and is untested with any values other
201201+ /// than strings solely consisting of alphanumeric characters
202202+ /// and underscores.
203203+ name: String,
204204+ /// This library thinks of a label as required, but will make one for you from
205205+ /// the name if you don't provide one via the `field` function. For
206206+ /// accessibility reasons, a field should always provide a label and all
207207+ /// the maintained form generators will output one.
208208+ label: String,
209209+ /// Optional help text for the field. This is used to provide additional
210210+ /// instructions or context for the field. It is up to the form generator
211211+ /// to decide if and how to display this text.
212212+ help_text: String,
213213+ /// Whether the field is disabled. A disabled field is not editable in
214214+ /// the browser. However, there is nothing stopping a user from changing
215215+ /// the value or submitting a different value via other means, so (presently)
216216+ /// this doesn't mean the value cannot be tampered with.
217217+ disabled: Bool,
218218+ )
219219+}
220220+221221+/// A `Definition` describes how an input works, e.g. how it looks and how it's
222222+/// parsed. Definitions are intended to be reusable.
173223///
174224/// The first role of a `Defintion` is to generate the HTML input for the field.
175225/// This library is format-agnostic and you can generate inputs as raw
···185235/// - [formz_lustre](https://hexdocs.pm/formz_lustre/)
186236///
187237/// How a definition parses an input value depends on whether a value is required
188188-/// for that input (i.e. whether `optional`, `require`, `list`, or `limited_list`
238238+/// for that input (i.e. whether `field`, `required_field`, `list`, or `limited_list`
189239/// was used to add it to the form). If a value is required, the definition is
190240/// expected to return a string error if the input is empty, or the `required` type
191241/// if it isn't. You can use the `definition` function to create a simple
···233283/// of how to write one.
234284///
235285/// This function takes as its only argument, the number of fields that already
236236-/// have a value. It should return a list of `Unvalidated` `InputState` items
237237-/// that specify if the value is required or not.
286286+/// have a value. It should return either `Ok` with a list of `Unvalidated`
287287+/// `InputState` items if it wants to offer more inputs to consumers of the
288288+/// form, or `Error` amount of inputs that were too many.
238289///
239290/// This is used multiple times... when the form is created so we know how many
240291/// initial inputs to present, when data is added so we know if we need to add
···262313/// ```
263314/// ```gleam
264315/// fn make_form() {
265265-/// use field1 <- formz.require(field("field1"), definitions.text_field())
266266-/// use field2 <- formz.require(field("field2"), definitions.text_field())
267267-/// use field3 <- formz.require(field("field3"), definitions.text_field())
316316+/// use field1 <- formz.required_field(field("field1"), definitions.text_field())
317317+/// use field2 <- formz.required_field(field("field2"), definitions.text_field())
318318+/// use field3 <- formz.required_field(field("field3"), definitions.text_field())
268319///
269320/// formz.create_form(#(field1, field2, field3))
270321/// }
···288339/// number of times. Don't do anything but create the type with the data you
289340/// need! If you need to do decoding that has side effects, you should use
290341/// `decode_then_try`.
291291-pub fn optional(
292292- field: field.Field,
342342+pub fn field(
343343+ config: Config,
293344 definition: Definition(widget, _, input_output),
294345 next: fn(input_output) -> Form(widget, form_output),
295346) -> Form(widget, form_output) {
296347 add_field(
297297- field,
348348+ config,
298349 Optional,
299350 definition.widget,
300351 definition.optional_parse(definition.parse, _),
···318369/// number of times. Don't do anything but create the type with the data you
319370/// need! If you need to do decoding that has side effects, you should use
320371/// `decode_then_try`.
321321-pub fn require(
322322- field: field.Field,
372372+pub fn required_field(
373373+ config: Config,
323374 is definition: Definition(widget, required_output, _),
324375 next next: fn(required_output) -> Form(widget, form_output),
325376) -> Form(widget, form_output) {
326377 add_field(
327327- field,
378378+ config,
328379 Required,
329380 definition.widget,
330381 definition.parse,
···334385}
335386336387fn add_field(
337337- field: field.Field,
388388+ config: Config,
338389 requirement: Requirement,
339390 widget: widget,
340391 parse_field: fn(String) -> Result(input_output, String),
···348399349400 // prepend the new field to the items from the form we got in the previous step.
350401 let updated_items = [
351351- Field(field, Unvalidated("", requirement), widget),
402402+ Field(config, Unvalidated("", requirement), widget),
352403 ..next_form.items
353404 ]
354405···483534/// }
484535pub fn limited_list(
485536 limit_check: fn(Int) -> Result(List(InputState), Int),
486486- field: field.Field,
537537+ config: Config,
487538 is definition: Definition(widget, required_output, _),
488539 next next: fn(List(required_output)) -> Form(widget, form_output),
489540) -> Form(widget, form_output) {
···495546 let initial_fields = limit_check(0) |> result.unwrap([])
496547 // prepend the new field to the items from the form we got in the previous step.
497548 let updated_items = [
498498- ListField(field, initial_fields, limit_check, definition.widget),
549549+ ListField(config, initial_fields, limit_check, definition.widget),
499550 ..next_form.items
500551 ]
501552···647698/// need! If you need to do decoding that has side effects, you should use
648699/// `decode_then_try`.
649700pub fn list(
650650- field: field.Field,
701701+ config: Config,
651702 is definition: Definition(widget, required_output, _),
652703 next next: fn(List(required_output)) -> Form(widget, form_output),
653704) -> Form(widget, form_output) {
654654- limited_list(limit_at_most(1_000_000), field, definition, next)
705705+ limited_list(limit_at_most(1_000_000), config, definition, next)
655706}
656707657708fn add_prefix_to_item(item: Item(widget), prefix: String) -> Item(widget) {
658709 case item {
659710 Field(item_details, state, widget) -> {
660660- let name = prefix <> "." <> item_details.name
661661- Field(item_details |> field.set_name(name), state, widget)
711711+ let new_name = prefix <> "." <> item_details.name
712712+ Field(item_details |> set_name(new_name), state, widget)
662713 }
663714 ListField(item_details, states, limit_check, widget) -> {
664664- let name = prefix <> "." <> item_details.name
665665- ListField(
666666- item_details |> field.set_name(name),
667667- states,
668668- limit_check,
669669- widget,
670670- )
715715+ let new_name = prefix <> "." <> item_details.name
716716+ ListField(item_details |> set_name(new_name), states, limit_check, widget)
671717 }
672718 SubForm(item_details, sub_items) -> {
673673- let name = prefix <> "." <> item_details.name
674674- SubForm(item_details |> subform.set_name(name), sub_items)
719719+ let new_name = prefix <> "." <> item_details.name
720720+ SubForm(item_details |> set_name(new_name), sub_items)
675721 }
676722 }
677723}
···689735/// need! If you need to do decoding that has side effects, you should use
690736/// `decode_then_try`.
691737pub fn subform(
692692- subform: subform.SubForm,
738738+ config: Config,
693739 form: Form(widget, sub_output),
694740 next: fn(sub_output) -> Form(widget, form_output),
695741) -> Form(widget, form_output) {
696742 let next_form = next(form.stub)
697743698698- let sub_items = form.items |> list.map(add_prefix_to_item(_, subform.name))
699699- let subform = SubForm(subform, sub_items)
744744+ let sub_items = form.items |> list.map(add_prefix_to_item(_, config.name))
745745+ let subform = SubForm(config, sub_items)
700746 let updated_items = [subform, ..next_form.items]
701747702748 let decode = fn(items: List(Item(widget))) {
···763809 data: Dict(String, List(String)),
764810) -> List(Item(widget)) {
765811 list.map(items, fn(item) {
766766- let values = dict.get(data, get_item_name(item))
812812+ let values = dict.get(data, item.config.name)
767813 case item, values {
768814 Field(detail, state, widget), Ok([_, ..] as values) -> {
769815 let assert Ok(last) = list.last(values)
···842888/// |> decode_then_try(fn(username, form) {
843889/// case is_username_taken(username) {
844890/// Ok(false) -> Ok(form)
845845-/// Ok(true) -> set_field_error(form, "username", "Username is taken")
891891+/// Ok(true) -> field_error(form, "username", "Username is taken")
846892/// }
847893/// }
848894pub fn decode_then_try(
···9871033/// let form = make_form()
9881034/// update_field(form, "name", field.set_label(_, "Full Name"))
9891035/// ```
990990-pub fn update_field(
10361036+pub fn update_config(
9911037 form: Form(widget, output),
9921038 name: String,
993993- fun: fn(field.Field) -> field.Field,
10391039+ fun: fn(Config) -> Config,
9941040) -> Form(widget, output) {
9951041 update(form, name, fn(item) {
9961042 case item {
997997- Field(field, ..) -> Field(..item, detail: fun(field))
998998- _ -> item
999999- }
10001000- })
10011001-}
10021002-10031003-/// Update the `ListField` [details]](https://hexdocs.pm/formz/formz/field.html) with
10041004-/// the given name using the provided function. If multiple items have the same
10051005-/// name, it will be called on all of them. If no items have the given name,
10061006-/// or an item with the given name exists but isn't a `ListField`, this function
10071007-/// will do nothing.
10081008-///
10091009-/// ```gleam
10101010-/// let form = make_form()
10111011-/// update(form, "name", field.set_label(_, "Full Name"))
10121012-/// ```
10131013-pub fn update_listfield(
10141014- form: Form(widget, output),
10151015- name: String,
10161016- fun: fn(field.Field) -> field.Field,
10171017-) -> Form(widget, output) {
10181018- update(form, name, fn(item) {
10191019- case item {
10201020- ListField(field, ..) -> ListField(..item, detail: fun(field))
10211021- _ -> item
10221022- }
10231023- })
10241024-}
10251025-10261026-/// Update the [`SubForm`](https://hexdocs.pm/formz/formz/subform.html) with
10271027-/// the given name using the provided function. If multiple subforms have the same
10281028-/// name, it will be called on all of them. If no items have the given name,
10291029-/// or an item with the given name exists but isn't a `SubForm`, this function
10301030-/// will do nothing.
10311031-///
10321032-/// ```gleam
10331033-/// let form = make_form()
10341034-/// update(form, "name", subform.set_help_text(_, "..."))
10351035-/// ```
10361036-pub fn update_subform(
10371037- form: Form(widget, output),
10381038- name: String,
10391039- fun: fn(subform.SubForm) -> subform.SubForm,
10401040-) -> Form(widget, output) {
10411041- update(form, name, fn(item) {
10421042- case item {
10431043- SubForm(subform, items) -> SubForm(fun(subform), items)
10441044- _ -> item
10431043+ Field(details, ..) -> Field(..item, config: fun(details))
10441044+ ListField(details, ..) -> ListField(..item, config: fun(details))
10451045+ SubForm(details, ..) -> SubForm(..item, config: fun(details))
10451046 }
10461047 })
10471048}
···10521053/// ### Example
10531054///
10541055/// ```
10551055-/// set_field_error(form, "username", "Username is taken")
10561056+/// field_error(form, "username", "Username is taken")
10561057/// ```
10571057-pub fn set_field_error(
10581058+pub fn field_error(
10581059 form: Form(widget, output),
10591060 name: String,
10601061 str: String,
···10721073/// takes a list of Results, where the Ok means the input is `Valid` and
10731074/// `Error` means the input is `Invalid` with the given error message.
10741075///
10761076+/// This does not clear any existing errors, it will just set the errors marked
10771077+/// in the input list. If you want to clear errors you'll have to use the
10781078+/// `update` function and do it manually.
10791079+///
10751080/// ### Example
10761081///
10771082/// ```
10781078-/// set_listfield_errors(form, "pet_names", [Ok(Nil), Ok(Nil), Error("Must be a cat")])
10831083+/// listfield_errors(form, "pet_names", [Ok(Nil), Ok(Nil), Error("Must be a cat")])
10791084/// ```
10801080-pub fn set_listfield_errors(
10851085+pub fn listfield_errors(
10811086 form: Form(widget, output),
10821087 name: String,
10831088 errors: List(Result(Nil, String)),
···11301135}
1131113611321137/// Create a `Definition` that can parse to any type if the field is optional.
11331133-/// This takes two functions. The first, `parse`, is the "required"" parse
11381138+/// This takes two functions. The first, `parse`, is the "required" parse
11341139/// function, which takes the raw string value, and turns it into the required
11351140/// type. The second, `optional_parse`, is a function that takes the normal
11361141/// parse function and the raw string value, and it is supposed to check the
11371137-/// input string: if it is empty, return an `Ok` with the `optional_stub`
11381138-/// value; and if it's not empty use the normal parse function.
11421142+/// input string: if it is empty, return an `Ok` with a value of the optional
11431143+/// type; and if it's not empty use the normal parse function.
11391144///
11401145/// See [formz_string](https://hexdocs.pm/formz_string/formz_string/definitions.html)
11411146/// for more examples of making widgets and definitions.
···12491254 }
12501255 })
12511256}
12571257+12581258+/// Create a field with the given name.
12591259+///
12601260+/// It uses
12611261+/// [justin.sentence_case](https://hexdocs.pm/justin/justin.html#sentence_case)
12621262+/// to create an initial label. You can override the label with the `set_label`
12631263+/// function. I don't know if this is very english-centric, so let me know if
12641264+/// this is a bad experience in other languages and I'll consider
12651265+/// something else.
12661266+///
12671267+/// ```gleam
12681268+/// field("name")
12691269+/// |> set_label("Full Name")
12701270+/// ```
12711271+pub fn named(name: String) -> Config {
12721272+ Config(
12731273+ name: name,
12741274+ label: justin.sentence_case(name),
12751275+ help_text: "",
12761276+ disabled: False,
12771277+ )
12781278+}
12791279+12801280+/// Set the name of the field. This is the key that will be used for the data.
12811281+pub fn set_name(config: Config, name: String) -> Config {
12821282+ Config(..config, name:)
12831283+}
12841284+12851285+/// Set the label of the field. This is the primary text that will be displayed
12861286+/// to the user about the field.
12871287+pub fn set_label(config: Config, label: String) -> Config {
12881288+ Config(..config, label:)
12891289+}
12901290+12911291+/// Additional instructions or help text to display to the user about the field.
12921292+pub fn set_help_text(config: Config, help_text: String) -> Config {
12931293+ Config(..config, help_text:)
12941294+}
12951295+12961296+/// Set the `disabled` flag directly.
12971297+pub fn set_disabled(config: Config, disabled: Bool) -> Config {
12981298+ Config(..config, disabled:)
12991299+}
13001300+13011301+/// Mark the form `Item` as disabled. This will prevent the user from
13021302+/// interacting with the field. If you do this for a subform, it will
13031303+/// only work if the form generator renders the subform as a `fieldset`.
13041304+/// For example, HTML does not allow you to mark inputs in a `<div>`
13051305+/// disabled as group but you can do this with a `<fieldset>`.
13061306+pub fn make_disabled(config: Config) -> Config {
13071307+ set_disabled(config, True)
13081308+}
-77
formz/src/formz/field.gleam
···11-//// A `Field` is the first argument needed to add a field to a form. It contains
22-//// information about this specific field, like it's name, label, or (optional)
33-//// help_text. There is a convenience function to create a field with just a name,
44-//// and then you can use the rest of the functions to set just the values you
55-//// need to change.
66-////
77-//// ```gleam
88-//// field("name") |> set_label("Full Name")
99-//// ```
1010-////
1111-//// ```gleam
1212-//// field("name")
1313-//// |> set_label("Full Name")
1414-//// |> set_help_text("You can make one up if you'd like.")
1515-//// ```
1616-1717-import justin
1818-1919-pub type Field {
2020- Field(
2121- /// The name of the field. The only truly required information for a field.
2222- /// This is used to identify the field in the form. It should be unique for
2323- /// each form, and is untested with any values other than strings solely
2424- /// consisting of alphanumeric characters and underscores.
2525- name: String,
2626- /// This library thinks of a label as required, but will make one for you from
2727- /// the name if you don't provide one via the `field` function. For
2828- /// accessibility reasons, a field should always provide a label and all
2929- /// the maintained form generators will output one.
3030- label: String,
3131- /// Optional help text for the field. This is used to provide additional
3232- /// instructions or context for the field. It is up to the form generator
3333- /// to decide if and how to display this text.
3434- help_text: String,
3535- /// Whether the field is disabled. A disabled field is not editable in
3636- /// the browser. However, there is nothing stopping a user from changing
3737- /// the value or submitting a different value via other means, so (presently)
3838- /// this doesn't mean the value cannot be tampered with.
3939- disabled: Bool,
4040- )
4141-}
4242-4343-/// Create a field with the given name. It uses [justin.sentence_case](https://hexdocs.pm/justin/justin.html#sentence_case)
4444-/// to create a label. You can override the label with the `set_label` function.
4545-///
4646-/// ```gleam
4747-/// field("name")
4848-/// |> set_label("Full Name")
4949-/// ```
5050-pub fn field(named name: String) -> Field {
5151- Field(
5252- name: name,
5353- label: justin.sentence_case(name),
5454- help_text: "",
5555- disabled: False,
5656- )
5757-}
5858-5959-pub fn set_name(field: Field, name: String) -> Field {
6060- Field(..field, name:)
6161-}
6262-6363-pub fn set_label(field: Field, label: String) -> Field {
6464- Field(..field, label:)
6565-}
6666-6767-pub fn set_help_text(field: Field, help_text: String) -> Field {
6868- Field(..field, help_text:)
6969-}
7070-7171-pub fn set_disabled(field: Field, disabled: Bool) -> Field {
7272- Field(..field, disabled:)
7373-}
7474-7575-pub fn make_disabled(field: Field) -> Field {
7676- set_disabled(field, True)
7777-}
-46
formz/src/formz/subform.gleam
···11-//// Details about a subform being added to a form. There is a convenience
22-//// function to create a field with just a name, and then you can use the rest
33-//// of the functions to set just the values you need to change.
44-55-import justin
66-77-pub type SubForm {
88- SubForm(
99- /// The name of the subform. This is used to prefix all the fields of the
1010- /// subform, so it should be unique for each subform added to aform.
1111- /// It is untested with any values other than strings consisting solely
1212- /// of alphanumeric characters and underscores.
1313- name: String,
1414- /// The label of the subform. This is completely optional, but if the
1515- /// subform is rendered inside a `<fieldset>` then it is [recommended](https://www.w3.org/WAI/tutorials/forms/grouping/)
1616- /// to have a `<legend>` with this label.
1717- label: String,
1818- /// Help text for the subform. There is less of a standard for this, but
1919- /// again, if rendered in a `<fieldset>` then `area-describedby` can be used
2020- /// to point to an element with this help text.
2121- help_text: String,
2222- )
2323-}
2424-2525-/// Create a subform with the given name. It uses [justin.sentence_case](https://hexdocs.pm/justin/justin.html#sentence_case)
2626-/// to create a label. You can override the label with the `set_label` function.
2727-///
2828-/// ```gleam
2929-/// subform("address")
3030-/// |> set_label("Shipping Address")
3131-/// ```
3232-pub fn subform(name) {
3333- SubForm(name, justin.sentence_case(name), "")
3434-}
3535-3636-pub fn set_name(sub: SubForm, name: String) -> SubForm {
3737- SubForm(..sub, name:)
3838-}
3939-4040-pub fn set_label(sub: SubForm, label: String) -> SubForm {
4141- SubForm(..sub, label:)
4242-}
4343-4444-pub fn set_help_text(sub: SubForm, help_text: String) -> SubForm {
4545- SubForm(..sub, help_text:)
4646-}
+32-34
formz/test/formz_test.gleam
···11import formz.{Invalid, Optional, Required, Unvalidated, Valid}
22-import formz/field.{field}
33-import formz/subform.{subform}
42import formz/validation
53import gleam/string
64import gleeunit
···6866}
69677068fn one_field_form() {
7171- use a <- formz.optional(field("a"), text_field())
6969+ use a <- formz.field(formz.named("a"), text_field())
7270 formz.create_form("hello " <> a)
7371}
74727573fn two_field_form() {
7676- use a <- formz.optional(field("a"), text_field())
7777- use b <- formz.optional(field("b"), text_field())
7474+ use a <- formz.field(formz.named("a"), text_field())
7575+ use b <- formz.field(formz.named("b"), text_field())
78767977 formz.create_form(#(a, b))
8078}
81798280fn three_field_form() {
8383- use a <- formz.optional(
8484- field("x") |> field.set_name("a") |> field.set_label("A"),
8181+ use a <- formz.field(
8282+ formz.named("x") |> formz.set_name("a") |> formz.set_label("A"),
8583 text_field()
8684 |> formz.verify(fn(str) {
8785 case string.length(str) > 3 {
···9189 }),
9290 )
93919494- use b <- formz.optional(
9595- field(named: "b"),
9292+ use b <- formz.field(
9393+ formz.named("b"),
9694 integer_field()
9795 |> formz.verify(fn(i) {
9896 case i > 0 {
···102100 }),
103101 )
104102105105- use c <- formz.optional(
106106- field(named: "c") |> field.set_name("c") |> field.set_label("C"),
103103+ use c <- formz.field(
104104+ formz.named("c") |> formz.set_name("c") |> formz.set_label("C"),
107105 float_field(),
108106 )
109107···167165pub fn parse_single_field_form_with_error_test() {
168166 let assert Error(f) =
169167 {
170170- use a <- formz.optional(field("a"), boolean_field())
168168+ use a <- formz.field(formz.named("a"), boolean_field())
171169 formz.create_form(a)
172170 }
173171 |> formz.data([#("a", "world")])
···236234237235pub fn sub_form_test() {
238236 let f1 = {
239239- use a <- formz.require(field("a"), integer_field())
240240- use b <- formz.require(field("b"), integer_field())
241241- use c <- formz.require(field("c"), integer_field())
237237+ use a <- formz.required_field(formz.named("a"), integer_field())
238238+ use b <- formz.required_field(formz.named("b"), integer_field())
239239+ use c <- formz.required_field(formz.named("c"), integer_field())
242240243241 formz.create_form(#(a, b, c))
244242 }
245243246244 let f2 = {
247247- use a <- formz.subform(subform("name"), f1)
248248- use b <- formz.require(field("d"), integer_field())
245245+ use a <- formz.subform(formz.named("name"), f1)
246246+ use b <- formz.required_field(formz.named("d"), integer_field())
249247250248 formz.create_form(#(a, b))
251249 }
···388386389387pub fn sub_form_error_test() {
390388 let f1 = {
391391- use a <- formz.optional(field("a"), integer_field())
392392- use b <- formz.require(field("b"), integer_field())
393393- use c <- formz.optional(field("c"), integer_field())
389389+ use a <- formz.field(formz.named("a"), integer_field())
390390+ use b <- formz.required_field(formz.named("b"), integer_field())
391391+ use c <- formz.field(formz.named("c"), integer_field())
394392395393 formz.create_form(#(a, b, c))
396394 }
397395398396 let f2 = {
399399- use a <- formz.subform(subform("name"), f1)
400400- use b <- formz.optional(field("d"), integer_field())
397397+ use a <- formz.subform(formz.named("name"), f1)
398398+ use b <- formz.field(formz.named("d"), integer_field())
401399402400 formz.create_form(#(a, b))
403401 }
···463461 // can change field
464462 let assert Error(form) =
465463 formz.decode_then_try(f, fn(form, _) {
466466- Error(form |> formz.set_field_error("a", "woops"))
464464+ Error(form |> formz.field_error("a", "woops"))
467465 })
468466 formz.get_states(form)
469467 |> should.equal([
···473471 ])
474472475473 let f = {
476476- use a <- formz.list(field("a"), float_field())
474474+ use a <- formz.list(formz.named("a"), float_field())
477475 formz.create_form(a)
478476 }
479477480478 formz.data(f, [#("a", "1"), #("a", "2")])
481479 |> formz.decode_then_try(fn(form, _) {
482480 Error(
483483- formz.set_listfield_errors(form, "a", [Error("woops 1"), Error("woops 2")]),
481481+ formz.listfield_errors(form, "a", [Error("woops 1"), Error("woops 2")]),
484482 )
485483 })
486484 |> get_form_from_error_result
···492490493491 formz.data(f, [#("a", "1"), #("a", "2")])
494492 |> formz.decode_then_try(fn(form, _) {
495495- Error(formz.set_listfield_errors(form, "a", [Error("woops"), Ok(Nil)]))
493493+ Error(formz.listfield_errors(form, "a", [Error("woops"), Ok(Nil)]))
496494 })
497495 |> get_form_from_error_result
498496 |> formz.get_states
···500498501499 formz.data(f, [#("a", "1"), #("a", "2")])
502500 |> formz.decode_then_try(fn(form, _) {
503503- Error(formz.set_listfield_errors(form, "a", [Ok(Nil), Error("woops")]))
501501+ Error(formz.listfield_errors(form, "a", [Ok(Nil), Error("woops")]))
504502 })
505503 |> get_form_from_error_result
506504 |> formz.get_states
···509507510508pub fn list_test() {
511509 let f = {
512512- use a <- formz.list(field("a"), float_field())
513513- use b <- formz.list(field("b"), float_field())
514514- use c <- formz.list(field("c"), float_field())
510510+ use a <- formz.list(formz.named("a"), float_field())
511511+ use b <- formz.list(formz.named("b"), float_field())
512512+ use c <- formz.list(formz.named("c"), float_field())
515513516514 formz.create_form(#(a, b, c))
517515 }
···602600 let zero_extra = {
603601 use a <- formz.limited_list(
604602 formz.simple_limit_check(1, 4, 0),
605605- field("a"),
603603+ formz.named("a"),
606604 integer_field(),
607605 )
608606···611609 let one_extra = {
612610 use a <- formz.limited_list(
613611 formz.simple_limit_check(1, 4, 1),
614614- field("a"),
612612+ formz.named("a"),
615613 integer_field(),
616614 )
617615···621619 let two_extra = {
622620 use a <- formz.limited_list(
623621 formz.simple_limit_check(1, 4, 2),
624624- field("a"),
622622+ formz.named("a"),
625623 integer_field(),
626624 )
627625···695693 let f = {
696694 use a <- formz.limited_list(
697695 formz.limit_between(2, 3),
698698- field("a"),
696696+ formz.named("a"),
699697 integer_field(),
700698 )
701699
···11import formz
22-import formz/field.{field}
32import formz_string/definition
43import formz_string/widget
5465pub fn make_form() {
77- use a <- formz.optional(field("text"), definition.text_field())
88- use b <- formz.optional(field("int"), definition.integer_field())
99- use c <- formz.optional(field("number"), definition.number_field())
1010- use d <- formz.optional(field("bool"), definition.boolean_field())
1111- use e <- formz.optional(field("email"), definition.email_field())
1212- use f <- formz.optional(field("password"), definition.password_field())
1313- use g <- formz.optional(
1414- field("choices"),
66+ use a <- formz.field(formz.named("text"), definition.text_field())
77+ use b <- formz.field(formz.named("int"), definition.integer_field())
88+ use c <- formz.field(formz.named("number"), definition.number_field())
99+ use d <- formz.field(formz.named("bool"), definition.boolean_field())
1010+ use e <- formz.field(formz.named("email"), definition.email_field())
1111+ use f <- formz.field(formz.named("password"), definition.password_field())
1212+ use g <- formz.field(
1313+ formz.named("choices"),
1514 definition.choices_field(letters(), A),
1615 )
1717- use h <- formz.optional(
1818- field("list"),
1616+ use h <- formz.field(
1717+ formz.named("list"),
1918 definition.list_field(["Dog", "Cat", "Ant"]),
2019 )
2121- use i <- formz.optional(
2222- field("textarea_widget"),
2020+ use i <- formz.field(
2121+ formz.named("textarea_widget"),
2322 definition.text_field()
2423 |> formz.widget(widget.textarea_widget()),
2524 )
2626-2725 formz.create_form(#(a, b, c, d, e, f, g, h, i))
2826}
2927
···11import formz
22-import formz/field.{field}
32import formz_string/definition
4354pub fn make_form() {
66- use name <- formz.require(field(named: "name"), is: definition.text_field())
77- use age <- formz.require(
88- field("age") |> field.set_label("Age"),
99- is: definition.integer_field(),
55+ use name <- formz.required_field(formz.named("name"), definition.text_field())
66+ use age <- formz.required_field(
77+ formz.named("age")
88+ |> formz.set_label("Age")
99+ |> formz.set_help_text("Please enter your age"),
1010+ definition.integer_field(),
1011 )
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"),
1515- is: definition.integer_field(),
1212+ use height <- formz.required_field(
1313+ formz.named("height")
1414+ |> formz.set_label("Height (cm)")
1515+ |> formz.set_help_text("Please enter your height in centimeters"),
1616+ definition.integer_field(),
1617 )
17181819 formz.create_form(#(name, age, height))
···11-import formz
22-import formz/field.{field}
11+import formz.{named}
32import formz_string/definition
43import formz_string/widget
5465pub fn make_form() {
77- use a <- formz.require(field("text"), definition.text_field())
88- use b <- formz.require(field("int"), definition.integer_field())
99- use c <- formz.require(field("number"), definition.number_field())
1010- use d <- formz.require(field("bool"), definition.boolean_field())
1111- use e <- formz.require(field("email"), definition.email_field())
1212- use f <- formz.require(field("password"), definition.password_field())
1313- use g <- formz.require(
1414- field("choices"),
66+ use a <- formz.required_field(named("text"), definition.text_field())
77+ use b <- formz.required_field(formz.named("int"), definition.integer_field())
88+ use c <- formz.required_field(
99+ formz.named("number"),
1010+ definition.number_field(),
1111+ )
1212+ use d <- formz.required_field(formz.named("bool"), definition.boolean_field())
1313+ use e <- formz.required_field(formz.named("email"), definition.email_field())
1414+ use f <- formz.required_field(
1515+ formz.named("password"),
1616+ definition.password_field(),
1717+ )
1818+ use g <- formz.required_field(
1919+ formz.named("choices"),
1520 definition.choices_field(letters(), stub: A),
1621 )
1717- use h <- formz.require(
1818- field("list"),
2222+ use h <- formz.required_field(
2323+ formz.named("list"),
1924 definition.list_field(["Dog", "Cat", "Ant"]),
2025 )
2121- use i <- formz.require(
2222- field("textarea_widget"),
2626+ use i <- formz.required_field(
2727+ formz.named("textarea_widget"),
2328 definition.text_field()
2429 |> formz.widget(widget.textarea_widget()),
2530 )
+11-10
formz_demo/src/formz_demo/examples/sub_form.gleam
···11import formz
22-import formz/field.{field}
33-import formz/subform.{subform}
42import formz_string/definition
5364pub fn make_form() {
77- use billing_address <- formz.subform(subform("billing"), address_form())
88- use shipping_address <- formz.subform(subform("shipping"), address_form())
55+ use billing_address <- formz.subform(formz.named("billing"), address_form())
66+ use shipping_address <- formz.subform(formz.named("shipping"), address_form())
97108 formz.create_form(#(billing_address, shipping_address))
119}
12101311fn address_form() {
1414- use street <- formz.require(field("street"), definition.text_field())
1515- use city <- formz.require(field("city"), definition.text_field())
1616- use state <- formz.require(
1717- field("state"),
1212+ use street <- formz.required_field(
1313+ formz.named("street"),
1414+ definition.text_field(),
1515+ )
1616+ use city <- formz.required_field(formz.named("city"), definition.text_field())
1717+ use state <- formz.required_field(
1818+ formz.named("state"),
1819 definition.list_field(states_list()),
1920 )
2020- use postal_code <- formz.require(
2121- field.field("postal_code"),
2121+ use postal_code <- formz.required_field(
2222+ formz.named("postal_code"),
2223 definition.text_field(),
2324 )
2425
+1-1
formz_lustre/gleam.toml
···1010formz = { path = "../formz" }
1111# formz = ">= 1.0.0 and < 2.0.0"
1212gleam_stdlib = ">= 0.34.0 and < 2.0.0"
1313-lustre = ">= 4.5.1 and < 5.0.0"
1313+lustre = ">= 4.6.3 and < 5.0.0"
14141515[dev-dependencies]
1616gleeunit = ">= 1.0.0 and < 2.0.0"
···11+---
22+version: 1.2.4
33+title: hidden field form no value
44+file: ./test/formz_string/simple_test.gleam
55+test_name: hidden_field_form_no_value_test
66+---
77+<div class="formz_items"><input type="hidden" name="a" value></div>