Select the types of activity you want to include in your feed.
API tweaks
I don’t even know. Just an absolute ton of little changes that add up to a lot of big changes. Trying to refine the API and make things as simple as possible. Not sure if I’m making any progress.
···203203204204However, often you want to parse a form, and then... you know... act on that
205205data, and in doing so you might discover more errors for the form. In this
206206-situation you can use `parse_then_try`:
206206+situation you can use `decode_then_try`:
207207208208```gleam
209209pub fn handle_form_submission(req: Request) -> Response {
···211211212212 let result = make_form()
213213 |> formz.data(formdata.values)
214214- |> formz.parse_then_try(fn(form, credentials) {
214214+ |> formz.decode_then_try(fn(form, credentials) {
215215 case credentials {
216216 #("admin" as username, "l33t") -> Ok(username)
217217 #("admin", _) ->
+752-272
formz/src/formz.gleam
···44//// makes the form using the `use` syntax, and then be able to use the form
55//// later for parsing or rendering in different contexts.
66////
77+//// You can use this cheatsheet to navigate the module documentation:
88+////
99+//// <table>
1010+//// <tr>
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>
1616+//// <a href="#list">list</a><br>
1717+//// <a href="#limited_list">limited_list</a><br>
1818+//// <a href="#subform">subform</a>
1919+//// </td>
2020+//// </tr>
2121+//// <tr>
2222+//// <td>Decoding and validating a form</td>
2323+//// <td>
2424+//// <a href="#data">data</a><br>
2525+//// <a href="#decode">decode</a><br>
2626+//// <a href="#decode_then_try">decode_then_try</a><br>
2727+//// <a href="#validate">validate</a><br>
2828+//// <a href="#validate_all">validate_all</a>
2929+//// </td>
3030+//// </tr>
3131+//// <tr>
3232+//// <td>Creating a field definition</td>
3333+//// <td>
3434+//// <a href="#definition">definition</a><br>
3535+//// <a href="#definition_with_custom_optional">definition_with_custom_optional</a><br>
3636+//// <a href="#transform">transform</a><br>
3737+//// <a href="#transform_with_custom_optional">transform_with_custom_optional</a><br>
3838+//// <a href="#verify">verify</a><br>
3939+//// <a href="#widget">widget</a>
4040+//// </td>
4141+//// </tr>
4242+//// <tr>
4343+//// <td>Creating a limited list</td>
4444+//// <td>
4545+//// <a href="#limit_at_least">limit_at_least</a><br>
4646+//// <a href="#limit_at_most">limit_at_most</a><br>
4747+//// <a href="#limit_between">limit_between</a><br>
4848+//// <a href="#simple_blank_fields">simple_blank_fields</a>
4949+//// </td>
5050+//// </tr>
5151+//// <tr>
5252+//// <td>Accessing and manipulating form fields</td>
5353+//// <td>
5454+//// <a href="#get">get</a><br>
5555+//// <a href="#items">items</a><br>
5656+//// <a href="#set_field_error">set_field_error</a><br>
5757+//// <a href="#set_listfield_errors">set_listfield_errors</a><br>
5858+//// <a href="#update">update</a><br>
5959+//// <a href="#update_field">update_field</a><br>
6060+//// <a href="#update_listfield">update_listfield</a><br>
6161+//// <a href="#update_subform">update_subform</a>
6262+//// </td>
6363+//// </tr>
6464+//// </table>
6565+////
766//// ### Examples
867////
968//// ```gleam
···1675//// fn process_form() {
1776//// make_form()
1877//// |> formz.data([#("name", "Louis"))])
1919-//// |> formz.parse
7878+//// |> formz.decode
2079//// # -> Ok("Louis")
2180//// }
2281//// ```
···3291//// fn process_form() {
3392//// make_form()
3493//// |> data([#("greeting", "Hello"), #("name", "World")])
3535-//// |> formz.parse
9494+//// |> formz.decode
3695//// # -> Ok("Hello World")
3796//// }
3897//// ```
39984040-import formz/definition.{type Definition, Definition}
4199import formz/field
42100import formz/subform
43101import gleam/dict.{type Dict}
102102+import gleam/int
44103import gleam/list
45104import gleam/option.{None, Some}
46105import gleam/result
106106+import gleam/string
471074848-/// The `widget` type is the set by the `Definition`s used to add fields for
108108+/// You create this using the `create_form` function.
109109+///
110110+/// The `widget` type is set by the `Definition`s used to add fields for
49111/// this form, and has the details of how to turn given fields into HTML inputs.
50112///
51113/// The `output` type is the type of the decoded data from the form. This is
5252-/// set by the `create_form` function, after all the fields have been added.
114114+/// set directly by the `create_form` function, after all the fields have
115115+/// been added.
53116pub opaque type Form(widget, output) {
54117 Form(
55118 items: List(FormItem(widget)),
5656- parse: fn(List(FormItem(widget))) -> Result(output, List(FormItem(widget))),
119119+ decode: fn(List(FormItem(widget))) -> Result(output, List(FormItem(widget))),
57120 stub: output,
58121 )
59122}
601236161-/// A form contains a list of fields and subforms. You primarily only use these
6262-/// when writing a form generator function. You can also manipulate these
6363-/// after a form has been created, to change things like labels, help text, etc.
6464-/// There are specific functions,`update_field` and `update_subform`, to help
6565-/// with this, so you don't have to pattern match when updating a specific item.
124124+/// A form is a decode function and a list of `FormItem`s. You add `FormItem`s
125125+/// to a form using the `optional`, `require`, `list`, etc functions. You
126126+/// primarily only use these directly when writing a form generator function to
127127+/// output your function to HTML.
128128+///
129129+/// You can also manipulate these after a form has been created, to change
130130+/// things like labels, help text, etc. There are specific functions,
131131+/// `update_field`, `update_listfield` and `update_subform`, to help with this,
132132+/// so you don't have to pattern match when updating a specific item.
66133///
67134/// ```
68135/// let form = make_form()
69136/// form.update_field("name", field.set_label(_, "Full Name"))
70137/// ```
71138pub type FormItem(widget) {
7272- Field(detail: field.Field, state: State, widget: widget)
7373- MultiField(detail: field.Field, states: List(State), widget: widget)
139139+ /// A single field that (generally speaking) corresponds to a single
140140+ /// HTML input
141141+ Field(detail: field.Field, state: FieldState, widget: widget)
142142+ /// A single field that a consumer can submit multiple values for.
143143+ ListField(
144144+ detail: field.Field,
145145+ states: List(FieldState),
146146+ blank_fields: fn(Int) -> List(FieldState),
147147+ widget: widget,
148148+ )
149149+ /// A group of fields that are added as and then parsed to a single unit.
74150 SubForm(detail: subform.SubForm, items: List(FormItem(widget)))
75151}
761527777-pub type State {
7878- Valid(value: String)
7979- Invalid(value: String, error: String)
153153+/// The state of the field, this is used to track the current raw value,
154154+/// whether the field is required or not, if the field has been validated,
155155+/// and the outcome of that validation.
156156+pub type FieldState {
157157+ Unvalidated(value: String, presence: FieldPresence)
158158+ Valid(value: String, presence: FieldPresence)
159159+ Invalid(value: String, presence: FieldPresence, error: String)
160160+}
161161+162162+/// Whether a field is required or not.
163163+pub type FieldPresence {
164164+ Optional
165165+ Required
166166+}
167167+168168+/// A `Definition` is the second argument needed to [add a field to a form](#require).
169169+/// It is what 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 [Field](https://hexdocs.pm/formz/formz/field.html);
171171+/// they take a bit more work to make as they are intended to be reusable.
172172+///
173173+/// The first role of a `Defintion` is to generate the HTML widget for the field.
174174+/// This library is format-agnostic and you can generate HTML widgets as raw
175175+/// strings, Lustre elements, Nakai nodes, something else, etc. There are
176176+/// currently three `formz` libraries that provide common field
177177+/// definitions (and widgets) for the most common HTML inputs.
178178+///
179179+/// - [formz_string](https://hexdocs.pm/formz_string/)
180180+/// - [formz_nakai](https://hexdocs.pm/formz_nakai/)
181181+/// - [formz_lustre](https://hexdocs.pm/formz_lustre/) (untested in a browser,
182182+/// would it be useful there??)
183183+///
184184+/// The second role of a `Definition` is to parse the data from the field.
185185+pub opaque type Definition(widget, required, optional) {
186186+ Definition(
187187+ widget: widget,
188188+ parse: fn(String) -> Result(required, String),
189189+ stub: required,
190190+ /// If a field is marked as optional, this function is called, with the
191191+ /// above parse as an argument. The idea is that this function will
192192+ /// call out to the parse function if the field is not empty, and
193193+ /// this should only handle the case where the raw input value is empty.
194194+ /// This function is necessary because not all fields should just be parsed
195195+ /// into an `Option` when they aren't provided.
196196+ /// For example, an optional text field might be an empty string,
197197+ /// an optional checkbox might be `False`, and an optional select might
198198+ /// be `option.None`.
199199+ optional_parse: fn(fn(String) -> Result(required, String), String) ->
200200+ Result(optional, String),
201201+ optional_stub: optional,
202202+ )
203203+}
204204+205205+type ListParsingResult(input_output) {
206206+ ListParsingResult(
207207+ value: String,
208208+ presence: FieldPresence,
209209+ output: Result(input_output, String),
210210+ )
80211}
8121282213/// Create an empty form that only parses to `thing`. This is primarily
···85216///
86217/// ```gleam
87218/// create_form(1)
8888-/// |> parse
219219+/// |> decode
89220/// # -> Ok(1)
90221/// ```
91222/// ```gleam
···94225/// use field2 <- formz.require(field("field2"), definitions.text_field())
95226/// use field3 <- formz.require(field("field3"), definitions.text_field())
96227///
9797-/// create_form(#(field1, field2, field3))
228228+/// formz.create_form(#(field1, field2, field3))
98229/// }
99230/// ```
100231pub fn create_form(thing: thing) -> Form(widget, thing) {
101232 Form([], fn(_) { Ok(thing) }, thing)
102233}
103234104104-fn add(
105105- detail: field.Field,
235235+/// Add an optional field to a form.
236236+///
237237+/// Ultimately whether a field is actually optional or not comes down to the
238238+/// details of the definition. The definition will receive the raw input string
239239+/// and is in charge of returning an error or an optional value.
240240+///
241241+/// If multiple values are submitted for this field, the last one will be used.
242242+///
243243+/// The final argument is a callback that will be called when the form
244244+/// is being... constructed to look for more fields; validated to check for
245245+/// errors; and decoded to parse the input data. **For this reason, the
246246+/// callback should be a function without side effects.** It can be called any
247247+/// number of times. Don't do anything but create the type with the data you
248248+/// need! If you need to do decoding that has side effects, you should use
249249+/// `decode_then_try` instead.
250250+pub fn optional(
251251+ field: field.Field,
252252+ definition: Definition(widget, _, input_output),
253253+ next: fn(input_output) -> Form(widget, form_output),
254254+) -> Form(widget, form_output) {
255255+ add_field(
256256+ field,
257257+ Optional,
258258+ definition.widget,
259259+ definition.optional_parse(definition.parse, _),
260260+ definition.optional_stub,
261261+ next,
262262+ )
263263+}
264264+265265+/// Add a required field to a form.
266266+///
267267+/// Ultimately whether a field is actually required or not comes down to the
268268+/// details of the definition. The definition will receive the raw input string
269269+/// and is in charge of returning an error or a value.
270270+///
271271+/// If multiple values are submitted for this field, the last one will be used.
272272+///
273273+/// The final argument is a callback that will be called when the form
274274+/// is being... constructed to look for more fields; validated to check for
275275+/// errors; and decoded to parse the input data. **For this reason, the
276276+/// callback should be a function without side effects.** It can be called any
277277+/// number of times. Don't do anything but create the type with the data you
278278+/// need! If you need to do decoding that has side effects, you should use
279279+/// `decode_then_try` instead.
280280+pub fn require(
281281+ field: field.Field,
282282+ is definition: Definition(widget, required_output, _),
283283+ next next: fn(required_output) -> Form(widget, form_output),
284284+) -> Form(widget, form_output) {
285285+ add_field(
286286+ field,
287287+ Required,
288288+ definition.widget,
289289+ definition.parse,
290290+ definition.stub,
291291+ next,
292292+ )
293293+}
294294+295295+fn add_field(
296296+ field: field.Field,
297297+ required: FieldPresence,
106298 widget: widget,
107299 parse_field: fn(String) -> Result(input_output, String),
108300 stub: input_output,
109109- rest fun: fn(input_output) -> Form(widget, form_output),
301301+ next: fn(input_output) -> Form(widget, form_output),
110302) -> Form(widget, form_output) {
111303 // we pass in our stub value, and we're going to throw away the
112304 // decoded result here, we just care about pulling out the fields
113305 // from the form.
114114- let next_form = fun(stub)
306306+ let next_form = next(stub)
115307116308 // prepend the new field to the items from the form we got in the previous step.
117117- let updated_items = [Field(detail, Valid(""), widget), ..next_form.items]
309309+ let updated_items = [
310310+ Field(field, Unvalidated("", required), widget),
311311+ ..next_form.items
312312+ ]
118313119119- // now create the parse function. parse function accepts most recent
314314+ // now create the decode function. decode function accepts most recent
120315 // version of input list, since data can be added to it. the list
121316 // above we just needed for the initial setup.
122122- let parse = fn(items: List(FormItem(widget))) {
317317+ let decode = fn(items: List(FormItem(widget))) {
123318 // pull out the latest version of this field to get latest input data
124319 let assert [Field(field, state, widget), ..next_items] = items
125320126126- // transform the input data using the transform/validate/decode/etc function
321321+ // transform the input data using the decode function
127322 let item_output = parse_field(state.value)
128323129324 // pass our transformed input data to the next function/form. if
130325 // we errored we still do this with our stub so we can continue
131326 // processing all the fields in the form. if we're on the error track
132327 // we'll throw away the "output" made with this and just keep the error
133133- let next_form = fun(item_output |> result.unwrap(stub))
134134- let form_output = next_form.parse(next_items)
328328+ let next_form = next(item_output |> result.unwrap(stub))
329329+ let form_output = next_form.decode(next_items)
135330136331 // ok, check which track we're on
137332 case form_output, item_output {
···141336 // form has errors, but this field was good, so add it to the list
142337 // of fields as is.
143338 Error(error_items), Ok(_) -> {
144144- let f = Field(field, Valid(state.value), widget)
339339+ let f = Field(field, Valid(state.value, required), widget)
145340 Error([f, ..error_items])
146341 }
147342148343 // form was good so far, but this field errored, so need to
149149- // mark this field as invalid and return all the fields we've got
150150- // so far
344344+ // mark this field as invalid, mark all the existing fields as valid,
345345+ // and return all the fields we've got so far
151346 Ok(_), Error(error) -> {
152152- let f = Field(field, Invalid(state.value, error), widget)
153153- Error([f, ..next_items])
347347+ let f = Field(field, Invalid(state.value, required, error), widget)
348348+ Error([f, ..mark_all_fields_as_valid(next_items)])
154349 }
155350156156- // form already has errors and this field errored, so add this field
157157- // to the list of errors
351351+ // form already has errors and this field errored, so mark this field
352352+ // as invalid, and add it to the list of errors
158353 Error(error_items), Error(error) -> {
159159- let f = Field(field, Invalid(state.value, error), widget)
354354+ let f = Field(field, Invalid(state.value, required, error), widget)
160355 Error([f, ..error_items])
161356 }
162357 }
163358 }
164164- Form(items: updated_items, parse:, stub: next_form.stub)
359359+ Form(items: updated_items, decode:, stub: next_form.stub)
165360}
166361167167-/// Add an optional field to a form.
168168-///
169169-/// This will use both the `parse` and `optional_parse` functions from the
170170-/// definition to parse the input data when parsing this field. Ultimately
171171-/// whether a field is actually optional or not comes down to the details
172172-/// of the definition.
173173-///
174174-/// The final argument is a callback that will be called when the form
175175-/// is being constructed to look for more fields; validated to check for errors;
176176-/// and when the form is being parsed, to decode the input data. **For this
177177-/// reason, the callback should be a function without side effects.** It can be
178178-/// called any number of times. Don't do anything but create the type with the
179179-/// data you need!
180180-pub fn optional(
181181- detail: field.Field,
182182- is definition: Definition(widget, _, input_output),
183183- rest fun: fn(input_output) -> Form(widget, form_output),
184184-) -> Form(widget, form_output) {
185185- add(
186186- detail |> field.set_required(False),
187187- definition.widget,
188188- definition.optional_parse(definition.parse, _),
189189- definition.optional_stub,
190190- fun,
191191- )
362362+pub fn simple_blank_fields(
363363+ min: Int,
364364+ max: Int,
365365+ extra: Int,
366366+) -> fn(Int) -> List(FieldState) {
367367+ fn(num_nonempty) {
368368+ int.max(1 - num_nonempty, int.min(extra, max - num_nonempty))
369369+ list.fold([1, extra, min], 0, int.max)
370370+371371+ // if they've specified a minimum required, then start there
372372+ let num_required = int.max(min - num_nonempty, 0)
373373+374374+ // at least one field needs to be present. don't want a form that is asking
375375+ // for no input
376376+ let num_base = case num_nonempty, num_required {
377377+ x, y if x > 0 || y > 0 -> 0
378378+ _, _ -> 1
379379+ }
380380+381381+ // they've asked for extra fields, add those unless doing so would
382382+ // exceed the max
383383+ let num_extra = int.min(extra, max - num_nonempty)
384384+385385+ // take whatever's bigger for our optional ones, either the bare minimum
386386+ // (base) or the extra if they've got room for it and they've asked for it
387387+ let num_optional = int.max(num_base, num_extra) - num_required
388388+389389+ list.append(
390390+ list.repeat(Unvalidated("", Required), num_required),
391391+ list.repeat(Unvalidated("", Optional), num_optional),
392392+ )
393393+ }
192394}
193395194194-/// Add a required field to a form.
396396+/// Convenience function for creating a `BlankFieldsFunc` with a minimum number
397397+/// of required values. This sets the maximum to `1,000,000`, effectively unlimited.
398398+pub fn limit_at_least(min: Int) {
399399+ simple_blank_fields(min, 1_000_000, 1)
400400+}
401401+402402+/// Convenience function for creating a `BlankFieldsFunc` with a maximum number
403403+/// of accepted values. This sets the minimum to `0`.
404404+pub fn limit_at_most(max: Int) {
405405+ simple_blank_fields(0, max, 1)
406406+}
407407+408408+/// Convenience function for creating a `BlankFieldsFunc` with a minimum and maximum
409409+/// number of values.
410410+pub fn limit_between(min: Int, max: Int) {
411411+ simple_blank_fields(min, max, 1)
412412+}
413413+414414+/// Add a list field to a form, but with limits on the number of values that
415415+/// can be submitted.
195416///
196196-/// This will use only the `parse` function from the definition to parse the
197197-/// input data when parsing this field. Ultimately whether a field is actually
198198-/// required or not comes down to the details of the definition.
417417+/// When adding a list field to a form, you have to provide a function that
418418+/// tells the form if and how many blank fields to provide for the
419419+/// consumer to fill out.
199420///
200200-/// This will also set the `required` value on the field to `True`. Form
201201-/// generators can use this to mark the HTML input elements as required for
202202-/// accessibility.
421421+/// For example, you are presenting an empty form to a consumer, and you
422422+/// want to show three blank fields for them to fill out, or you want to always
423423+/// show one more blank field than the number of values that already belong to
424424+/// the form, etc.
425425+///
426426+/// This function takes as its only argument, the number of fields that already
427427+/// have a value. It should return a list of `Unvalidated` `FieldState` items
428428+/// that specify if the value is required or not.
429429+///
430430+/// There are helper functions, `limit_at_least`, `limit_at_most`, and
431431+/// `limit_between` or more generally `simple_blank_fields` for the most common
432432+/// use cases.
203433///
204434/// The final argument is a callback that will be called when the form
205205-/// is being constructed to look for more fields; validated to check for errors;
206206-/// and when the form is being parsed, to decode the input data. **For this
207207-/// reason, the callback should be a function without side effects.** It can be
208208-/// called any number of times. Don't do anything but create the type with the
209209-/// data you need!
210210-pub fn require(
211211- detail: field.Field,
212212- is definition: Definition(widget, required_output, _),
213213- rest fun: fn(required_output) -> Form(widget, form_output),
214214-) -> Form(widget, form_output) {
215215- add(
216216- detail |> field.set_required(True),
217217- definition.widget,
218218- definition.parse,
219219- definition.stub,
220220- fun,
221221- )
222222-}
223223-224224-pub fn list(
225225- detail: field.Field,
435435+/// is being... constructed to look for more fields; validated to check for
436436+/// errors; and decoded to parse the input data. **For this reason, the
437437+/// callback should be a function without side effects.** It can be called any
438438+/// number of times. Don't do anything but create the type with the data you
439439+/// need! If you need to do decoding that has side effects, you should use
440440+/// `decode_then_try` instead.
441441+pub fn limited_list(
442442+ blank_fields: fn(Int) -> List(FieldState),
443443+ field: field.Field,
226444 is definition: Definition(widget, required_output, _),
227227- rest fun: fn(List(required_output)) -> Form(widget, form_output),
445445+ next next: fn(List(required_output)) -> Form(widget, form_output),
228446) -> Form(widget, form_output) {
229447 // we pass in our stub value, and we're going to throw away the
230448 // decoded result here, we just care about pulling out the fields
231449 // from the form.
232232- let next_form = fun([definition.stub])
450450+ let next_form = next([definition.stub])
233451234452 // prepend the new field to the items from the form we got in the previous step.
235453 let updated_items = [
236236- MultiField(detail, [Valid("")], definition.widget),
454454+ ListField(field, blank_fields(0), blank_fields, definition.widget),
237455 ..next_form.items
238456 ]
239457240240- // now create the parse function. parse function accepts most recent
458458+ // now create the decode function. decode function accepts most recent
241459 // version of input list, since data can be added to it. the list
242460 // above we just needed for the initial setup.
243243- let parse = fn(items: List(FormItem(widget))) {
461461+ let decode = fn(items: List(FormItem(widget))) {
244462 // pull out the latest version of this field to get latest input data
245245- let assert [MultiField(field, states, widget), ..next_items] = items
463463+ let assert [ListField(field, states, blank_fields, widget), ..next_items] =
464464+ items
246465247247- // transform the input data using the transform/validate/decode/etc function
466466+ // go through all decode all input values. these can have empty rows
467467+ // that were offered to the consumer, but they didn't fill out. we need
468468+ // to keep track of those so they can be used when generating the form
469469+ // again on error
248470 let item_results =
249249- states
250250- |> list.map(fn(state) { #(state.value, definition.parse(state.value)) })
471471+ list.map(states, parse_list_state(_, definition.parse, definition.stub))
251472252252- let item_outputs = item_results |> list.map(fn(t) { t.1 }) |> result.all
473473+ // but we don't want them for considering the output of this list field,
474474+ // so remove the empty optional fields and just grab the outputs
475475+ let item_outputs =
476476+ item_results |> outputs_for_required_or_nonempty |> result.all
253477254478 // pass our transformed input data to the next function/form. if
255479 // we errored we still do this with our stub so we can continue
256480 // processing all the fields in the form. if we're on the error track
257481 // we'll throw away the "output" made with this and just keep the error
258258- let next_form = fun(item_outputs |> result.unwrap([definition.stub]))
259259- let form_output = next_form.parse(next_items)
482482+ let next_form = next(item_outputs |> result.unwrap([definition.stub]))
483483+ let form_output = next_form.decode(next_items)
260484261485 // ok, check which track we're on
262486 case form_output, item_outputs {
263487 // everything is good! pass along the output
264488 Ok(_), Ok(_) -> form_output
265489266266- // form has errors, but this field was good, so add it to the list
267267- // of fields as is.
490490+ // form has errors, but this field was good, so mark all states as valid
268491 Error(error_items), Ok(_) -> {
269269- let f =
270270- MultiField(
271271- field,
272272- states |> list.map(fn(s) { Valid(s.value) }),
273273- widget,
274274- )
492492+ let states = states |> list.map(fn(s) { Valid(s.value, s.presence) })
493493+ let f = ListField(field, states, blank_fields, widget)
275494 Error([f, ..error_items])
276495 }
277496278497 // form was good so far, but this field errored, so need to
279279- // mark this field as invalid and return all the fields we've got
280280- // so far
498498+ // mark the errored states as invalid, and mark the successful fields as
499499+ // valid, and then return all these fields we've got so far
281500 Ok(_), Error(_) -> {
282282- let f =
283283- MultiField(
284284- field,
285285- item_results
286286- |> list.map(fn(t) {
287287- case t.1 {
288288- Ok(_) -> Valid(t.0)
289289- Error(e) -> Invalid(t.0, e)
290290- }
291291- }),
292292- widget,
293293- )
294294- Error([f, ..next_items])
501501+ let states = list.map(item_results, state_from_parse_result)
502502+ let f = ListField(field, states, blank_fields, widget)
503503+ Error([f, ..mark_all_fields_as_valid(next_items)])
295504 }
296505297506 // form already has errors and this field errored, so add this field
298298- // to the list of errors
507507+ // to the list of errors, but first marking the invalid states as... invalid
299508 Error(error_items), Error(_) -> {
300300- let f =
301301- MultiField(
302302- field,
303303- item_results
304304- |> list.map(fn(t) {
305305- case t.1 {
306306- Ok(_) -> Valid(t.0)
307307- Error(e) -> Invalid(t.0, e)
308308- }
309309- }),
310310- widget,
311311- )
509509+ let states = list.map(item_results, state_from_parse_result)
510510+ let f = ListField(field, states, blank_fields, widget)
312511 Error([f, ..error_items])
313512 }
314513 }
315514 }
316316- Form(items: updated_items, parse:, stub: next_form.stub)
515515+516516+ Form(items: updated_items, decode:, stub: next_form.stub)
517517+}
518518+519519+fn state_from_parse_result(
520520+ result: ListParsingResult(input_output),
521521+) -> FieldState {
522522+ let ListParsingResult(value, presence, output) = result
523523+ case output {
524524+ Ok(_) -> Valid(value, presence)
525525+ Error(error) -> Invalid(value, presence, error)
526526+ }
527527+}
528528+529529+fn outputs_for_required_or_nonempty(
530530+ states: List(ListParsingResult(output)),
531531+) -> List(Result(output, String)) {
532532+ list.filter_map(states, fn(result) {
533533+ case result.value, result.presence {
534534+ "", Optional -> Error(Nil)
535535+ _, _ -> Ok(result.output)
536536+ }
537537+ })
538538+}
539539+540540+fn parse_list_state(
541541+ state: FieldState,
542542+ parse: fn(String) -> Result(a, String),
543543+ stub: a,
544544+) -> ListParsingResult(a) {
545545+ case state.value, state.presence {
546546+ "", Required -> parse(state.value)
547547+ "", Optional -> Ok(stub)
548548+ _, _ -> parse(state.value)
549549+ }
550550+ |> ListParsingResult(state.value, state.presence, _)
551551+}
552552+553553+/// Add a list field to a form, but with no limits on the number of values that
554554+/// can be submitted. A list field is like a normal field except a consumer can
555555+/// submit multiple values, and it will return a `List` of the parsed values.
556556+///
557557+/// The final argument is a callback that will be called when the form
558558+/// is being... constructed to look for more fields; validated to check for
559559+/// errors; and decoded to parse the input data. **For this reason, the
560560+/// callback should be a function without side effects.** It can be called any
561561+/// number of times. Don't do anything but create the type with the data you
562562+/// need! If you need to do decoding that has side effects, you should use
563563+/// `decode_then_try` instead.
564564+pub fn list(
565565+ field: field.Field,
566566+ is definition: Definition(widget, required_output, _),
567567+ next next: fn(List(required_output)) -> Form(widget, form_output),
568568+) -> Form(widget, form_output) {
569569+ limited_list(limit_at_most(1_000_000), field, definition, next)
570570+}
571571+572572+fn add_prefix_to_item(
573573+ item: FormItem(widget),
574574+ prefix: String,
575575+) -> FormItem(widget) {
576576+ case item {
577577+ Field(item_details, state, widget) -> {
578578+ let name = prefix <> "." <> item_details.name
579579+ Field(item_details |> field.set_name(name), state, widget)
580580+ }
581581+ ListField(item_details, states, blank_fields, widget) -> {
582582+ let name = prefix <> "." <> item_details.name
583583+ ListField(
584584+ item_details |> field.set_name(name),
585585+ states,
586586+ blank_fields,
587587+ widget,
588588+ )
589589+ }
590590+ SubForm(item_details, sub_items) -> {
591591+ let name = prefix <> "." <> item_details.name
592592+ SubForm(item_details |> subform.set_name(name), sub_items)
593593+ }
594594+ }
317595}
318596319597/// Add a form as a subform. This will essentially append the fields from the
···322600/// can be marked up as a group for accessibility reasons.
323601///
324602/// The final argument is a callback that will be called when the form
325325-/// is being constructed to look for more fields; validated to check for errors;
326326-/// and when the form is being parsed, to decode the input data. **For this
327327-/// reason, the callback should be a function without side effects.** It can be
328328-/// called any number of times. Don't do anything but create the type with the
329329-/// data you need!
603603+/// is being... constructed to look for more fields; validated to check for
604604+/// errors; and decoded to parse the input data. **For this reason, the
605605+/// callback should be a function without side effects.** It can be called any
606606+/// number of times. Don't do anything but create the type with the data you
607607+/// need! If you need to do decoding that has side effects, you should use
608608+/// `decode_then_try` instead.
330609pub fn subform(
331331- details: subform.SubForm,
332332- sub: Form(widget, sub_output),
333333- fun: fn(sub_output) -> Form(widget, form_output),
610610+ subform: subform.SubForm,
611611+ form: Form(widget, sub_output),
612612+ next: fn(sub_output) -> Form(widget, form_output),
334613) -> Form(widget, form_output) {
335335- let next_form = fun(sub.stub)
336336-337337- let sub_items =
338338- sub.items
339339- |> list.map(fn(item) {
340340- case item {
341341- Field(item_details, state, widget) -> {
342342- let name = details.name <> "." <> item_details.name
343343- Field(item_details |> field.set_name(name), state, widget)
344344- }
345345- MultiField(item_details, states, widget) -> {
346346- let name = details.name <> "." <> item_details.name
347347- MultiField(item_details |> field.set_name(name), states, widget)
348348- }
349349- SubForm(item_details, sub_items) -> {
350350- let name = details.name <> "." <> item_details.name
351351- SubForm(item_details |> subform.set_name(name), sub_items)
352352- }
353353- }
354354- })
614614+ let next_form = next(form.stub)
355615356356- let updated_items = [SubForm(details, sub_items), ..next_form.items]
616616+ let sub_items = form.items |> list.map(add_prefix_to_item(_, subform.name))
617617+ let subform = SubForm(subform, sub_items)
618618+ let updated_items = [subform, ..next_form.items]
357619358358- let parse = fn(items: List(FormItem(widget))) {
620620+ let decode = fn(items: List(FormItem(widget))) {
359621 // pull out the latest version of this field to get latest input data
360622 let assert [SubForm(details, sub_items), ..next_items] = items
361623362362- let item_output = sub.parse(sub_items)
624624+ let item_output = form.decode(sub_items)
363625364364- let next_form = fun(item_output |> result.unwrap(sub.stub))
365365- let form_output = next_form.parse(next_items)
626626+ let next_form = next(item_output |> result.unwrap(form.stub))
627627+ let form_output = next_form.decode(next_items)
366628367629 // ok, check which track we're on
368630 case form_output, item_output {
···371633372634 // form has errors, but this sub form was good, so add it to the list
373635 // of items as is.
374374- Error(next_error_items), Ok(_) ->
375375- Error([SubForm(details, sub_items), ..next_error_items])
636636+ Error(next_error_items), Ok(_) -> {
637637+ let sub = SubForm(details, sub_items |> mark_all_fields_as_valid)
638638+ Error([sub, ..next_error_items])
639639+ }
376640377641 // form was good so far, but this sub form errored, so need to
378642 // hop on error track
379379- Ok(_), Error(error_items) ->
380380- Error([SubForm(details, error_items), ..next_items])
643643+ Ok(_), Error(error_items) -> {
644644+ let sub = SubForm(details, error_items)
645645+ Error([sub, ..mark_all_fields_as_valid(next_items)])
646646+ }
381647382648 // form already has errors and this form errored, so add this field
383649 // to the list of errors
384384- Error(next_error_items), Error(error_items) ->
385385- Error([SubForm(details, error_items), ..next_error_items])
650650+ Error(next_error_items), Error(error_items) -> {
651651+ let sub = SubForm(details, error_items)
652652+ Error([sub, ..next_error_items])
653653+ }
386654 }
387655 }
388388- Form(updated_items, parse: parse, stub: next_form.stub)
389389-}
390390-391391-@internal
392392-pub fn get_states(form: Form(widget, output)) -> List(State) {
393393- form.items |> do_get_states |> list.reverse
394394-}
395395-396396-fn do_get_states(items: List(FormItem(widget))) -> List(State) {
397397- list.fold(items, [], fn(acc, item) {
398398- case item {
399399- Field(_, state, _) -> [state, ..acc]
400400- MultiField(_, states, _) -> list.append(states |> list.reverse, acc)
401401- SubForm(_, sub_items) -> list.flatten([do_get_states(sub_items), acc])
402402- }
403403- })
656656+ Form(updated_items, decode:, stub: next_form.stub)
404657}
405658406659/// Add input data to this form. This will set the raw string value of the fields.
···410663///
411664/// The input data is a list of tuples, where the first element is the name of the
412665/// field and the second element is the value to set. If the field does not exist
413413-/// the data is ignored, and if multiple values are given for the same field, the
414414-/// last one wins.
666666+/// the data is ignored.
415667///
416416-/// This resets the error state of the fields that have data, so you'll need to
417417-/// re-validate the form after setting data.
668668+/// This resets the validation state of the fields that have data, so you'll need to
669669+/// re-validate or decode the form after setting data.
418670pub fn data(
419671 form: Form(widget, output),
420672 input_data: List(#(String, String)),
421673) -> Form(widget, output) {
422674 let data = to_dict(input_data)
423423- let Form(items, parse, stub) = form
424424- Form(do_data(items, data), parse, stub)
675675+ let Form(items, decode, stub) = form
676676+ Form(do_data(items, data), decode, stub)
425677}
426678427427-pub fn do_data(
679679+fn do_data(
428680 items: List(FormItem(widget)),
429681 data: Dict(String, List(String)),
430682) -> List(FormItem(widget)) {
431683 list.map(items, fn(item) {
432432- case item {
433433- Field(detail, _, widget) ->
434434- case dict.get(data, item.detail.name) {
435435- Ok([first, ..]) -> {
436436- Field(detail, Valid(first), widget)
437437- }
438438- _ -> item
439439- }
440440- MultiField(detail, _, widget) -> {
441441- case dict.get(data, item.detail.name) {
442442- Ok(values) -> {
443443- let values = list.map(values, fn(v) { Valid(v) }) |> list.reverse
444444- MultiField(detail, values, widget)
445445- }
684684+ let values = dict.get(data, get_item_name(item))
685685+ case item, values {
686686+ Field(detail, state, widget), Ok([_, ..] as values) -> {
687687+ let assert Ok(last) = list.last(values)
688688+ Field(detail, Unvalidated(last, state.presence), widget)
689689+ }
690690+ ListField(detail, states, blank_fields, widget), Ok(values) -> {
691691+ let nonempty = list.filter(values, fn(v) { !string.is_empty(v) })
692692+ let num_nonempty = list.length(nonempty)
446693447447- _ -> item
448448- }
694694+ let items =
695695+ list.map2(states, nonempty, fn(s, v) { Unvalidated(v, s.presence) })
696696+ |> list.append(
697697+ nonempty
698698+ |> list.drop(list.length(states))
699699+ |> list.map(fn(v) { Unvalidated(v, Optional) }),
700700+ )
701701+ |> list.append(blank_fields(num_nonempty))
702702+703703+ ListField(detail, items, blank_fields, widget)
449704 }
450450- SubForm(detail, items) -> SubForm(detail, do_data(items, data))
705705+ SubForm(detail, items), _ -> SubForm(detail, do_data(items, data))
706706+ _, _ -> item
451707 }
452708 })
453709}
454710455711fn to_dict(values: List(#(String, String))) -> Dict(String, List(String)) {
456456- list.fold_right(values |> list.reverse, dict.new(), fn(acc, kv) {
712712+ list.fold_right(values, dict.new(), fn(acc, kv) {
457713 let #(key, value) = kv
458714 dict.upsert(acc, key, fn(opt) {
459715 case opt {
···464720 })
465721}
466722467467-/// Parse the form. This means step through the fields one by one, parsing
723723+/// Decode the form. This means step through the fields one by one, parsing
468724/// them individually. If any field fails to parse, the whole form is considered
469725/// invalid, however it will still continue parsing the rest of the fields to
470726/// collect all errors. This is useful for showing all errors at once. If no
471727/// fields fail to parse, the decoded value is returned, which is the value given
472728/// to `create_form`.
473729///
474474-/// If you'd like to parse the form but not get the output, so you can give
730730+/// If you'd like to decode the form but not get the output, so you can give
475731/// feedback to a user in response to input, you can use `validate` or `validate_all`.
476476-pub fn parse(form: Form(widget, output)) -> Result(output, Form(widget, output)) {
477477- case form.parse(form.items) {
732732+pub fn decode(
733733+ form: Form(widget, output),
734734+) -> Result(output, Form(widget, output)) {
735735+ case form.decode(form.items) {
478736 Ok(output) -> Ok(output)
479737 Error(items) -> Error(Form(..form, items:))
480738 }
481739}
482740483741/// Parse the form, then apply a function to the output if it was successful.
484484-/// This is a very thin wrapper around `parse` and `result.try`, but the
742742+/// This is a very thin wrapper around `decode` and `result.try`, but the
485743/// difference being it will pass the form along to the function as a second
486744/// argument in addition to the successful result. This allows you to easily
487745/// update the form fields with errors or other information based on the output.
···490748/// aren't easily checked in simple parsing functions. Like, say, hitting a
491749/// db to check if a username is taken.
492750///
751751+/// As a reminder, parse functions will be called multiple times for each field
752752+/// when the form is being made, validated and parsed, and should not contain
753753+/// side effects. This function is the proper way to add errors to fields from
754754+/// functions that have side effects.
755755+///
493756/// ```gleam
494757/// make_form()
495758/// |> data(form_data)
496496-/// |> parse_then_try(fn(username, form) {
759759+/// |> decode_then_try(fn(username, form) {
497760/// case is_username_taken(username) {
498761/// Ok(false) -> Ok(form)
499499-/// Ok(true) -> update_field(form, "username", field.set_error(_, "Username is taken"))
762762+/// Ok(true) -> set_field_error(form, "username", "Username is taken")
500763/// }
501764/// }
502502-pub fn parse_then_try(
765765+pub fn decode_then_try(
503766 form: Form(widget, output),
504767 apply fun: fn(Form(widget, output), output) -> Result(c, Form(widget, output)),
505768) -> Result(c, Form(widget, output)) {
506506- form |> parse |> result.try(fun(form, _))
769769+ form
770770+ |> decode
771771+ |> result.try(fn(output) {
772772+ let items = form.items |> mark_all_fields_as_valid
773773+ fun(Form(..form, items:), output)
774774+ })
507775}
508776509509-/// Validate specific fields of the form. This is similar to `parse`, but
777777+fn mark_all_fields_as_valid(
778778+ items: List(FormItem(widget)),
779779+) -> List(FormItem(widget)) {
780780+ list.map(items, fn(item) {
781781+ case item {
782782+ Field(field, state, widget) ->
783783+ Field(field, Valid(state.value, state.presence), widget)
784784+ ListField(field, states, blank_fields, widget) -> {
785785+ let new_states =
786786+ states
787787+ |> list.map(fn(state) { Valid(state.value, state.presence) })
788788+ ListField(field, new_states, blank_fields, widget)
789789+ }
790790+ SubForm(subform, items) ->
791791+ SubForm(subform, mark_all_fields_as_valid(items))
792792+ }
793793+ })
794794+}
795795+796796+/// Validate specific fields of the form. This is similar to `decode`, but
510797/// instead of returning the decoded output if there are no errors, it returns
511798/// the valid form. This is useful for if you want to be able to give feedback
512512-/// to the user about whether certain fields are valid or not. In this case you
799799+/// to the user about whether certain fields are valid or not. For example, you
513800/// could just validate only fields that the user has interacted with.
514801pub fn validate(
515802 form: Form(widget, output),
516803 names: List(String),
517804) -> Form(widget, output) {
518518- case form.parse(form.items) {
519519- Ok(_) -> form
805805+ case form.decode(form.items) {
806806+ Ok(_) -> {
807807+ let items =
808808+ list.map(form.items, fn(parsed_item) {
809809+ let item_name = get_item_name(parsed_item)
810810+ case list.find(names, fn(name) { item_name == name }) {
811811+ Ok(_) -> {
812812+ let assert Ok(item) =
813813+ [parsed_item] |> mark_all_fields_as_valid |> list.first
814814+ item
815815+ }
816816+ Error(_) -> {
817817+ let assert Ok(original_item) = get(form, item_name)
818818+ original_item
819819+ }
820820+ }
821821+ })
822822+ Form(..form, items:)
823823+ }
520824 Error(items) -> {
521825 let items =
522826 list.map(items, fn(parsed_item) {
523523- let item_name = case parsed_item {
524524- Field(field, _, _) -> field.name
525525- MultiField(field, _, _) -> field.name
526526- SubForm(subform, _) -> subform.name
527527- }
827827+ let item_name = get_item_name(parsed_item)
528828 case list.find(names, fn(name) { item_name == name }) {
529829 Ok(_) -> parsed_item
530830 Error(_) -> {
···538838 }
539839}
540840541541-/// Validate all the fields in the form. This is similar to `parse`, but
841841+/// Validate all the fields in the form. This is similar to `decode`, but
542842/// instead of returning the decoded output if there are no errors, it returns
543843/// the valid form. This is useful for if you want to be able to give feedback
544844/// to the user about whether certain fields are valid or not.
545845pub fn validate_all(form: Form(widget, output)) -> Form(widget, output) {
546546- let names =
547547- form.items
548548- |> list.map(fn(f) {
549549- case f {
550550- Field(field, _, _) -> field.name
551551- MultiField(field, _, _) -> field.name
552552- SubForm(subform, _) -> subform.name
553553- }
554554- })
555555-556556- validate(form, names)
846846+ form.items |> list.map(get_item_name) |> validate(form, _)
557847}
558848559849/// Get each [`FormItem`](https://hexdocs.pm/formz/formz.html#FormItem) added
560560-/// to the form. Any time a field or subform are added, a FormItem is created.
850850+/// to the form. Any time a field, list field, or subform are added, a FormItem is created.
561851pub fn items(form: Form(widget, output)) -> List(FormItem(widget)) {
562852 form.items
563853}
···568858 form: Form(widget, output),
569859 name: String,
570860) -> Result(FormItem(widget), Nil) {
571571- list.find(form.items, fn(item) {
572572- let item_name = case item {
573573- Field(field, _, _) -> field.name
574574- MultiField(field, _, _) -> field.name
575575- SubForm(subform, _) -> subform.name
576576- }
577577- item_name == name
578578- })
861861+ list.find(form.items, fn(item) { name == get_item_name(item) })
579862}
580863581864/// Update the [`FormItem`](https://hexdocs.pm/formz/formz.html#FormItem) with
···586869 name: String,
587870 fun: fn(FormItem(widget)) -> FormItem(widget),
588871) {
589589- let items = do_formitems_update(form.items, name, fun)
872872+ let items = do_update(form.items, name, fun)
590873 Form(..form, items:)
591874}
592875593593-fn do_formitems_update(
876876+fn do_update(
594877 items: List(FormItem(widget)),
595878 name: String,
596879 fun: fn(FormItem(widget)) -> FormItem(widget),
···598881 list.map(items, fn(item) {
599882 case item {
600883 Field(detail, _, _) if detail.name == name -> fun(item)
601601- MultiField(detail, _, _) if detail.name == name -> fun(item)
602602- SubForm(detail, _) if detail.name == name -> fun(item)
603603- SubForm(detail, items) ->
604604- SubForm(detail, do_formitems_update(items, name, fun))
884884+ ListField(detail, _, _, _) if detail.name == name -> fun(item)
885885+ SubForm(detail, items) -> {
886886+ let sub = SubForm(detail, do_update(items, name, fun))
887887+ case detail.name == name {
888888+ True -> fun(sub)
889889+ False -> sub
890890+ }
891891+ }
605892 _ -> item
606893 }
607894 })
···609896610897/// Update the [`Field`](https://hexdocs.pm/formz/formz/field.html) with
611898/// the given name using the provided function. If multiple items have the same
612612-/// name, it will be called on all of them.
899899+/// name, it will be called on all of them. If no items have the given name,
900900+/// or an item with the given name exists but isn't a `Field`, this function
901901+/// will do nothing.
613902///
614903/// ```gleam
615904/// let form = make_form()
···622911) -> Form(widget, output) {
623912 update(form, name, fn(item) {
624913 case item {
625625- Field(field, state, widget) -> Field(fun(field), state, widget)
626626- MultiField(field, states, widget) ->
627627- MultiField(fun(field), states, widget)
628628- SubForm(..) -> item
914914+ Field(field, ..) -> Field(..item, detail: fun(field))
915915+ _ -> item
629916 }
630917 })
631918}
632919920920+/// Update the [`ListField`](https://hexdocs.pm/formz/formz/field.html) with
921921+/// the given name using the provided function. If multiple items have the same
922922+/// name, it will be called on all of them. If no items have the given name,
923923+/// or an item with the given name exists but isn't a `ListField`, this function
924924+/// will do nothing.
925925+///
926926+/// ```gleam
927927+/// let form = make_form()
928928+/// update(form, "name", field.set_label(_, "Full Name"))
929929+/// ```
930930+pub fn update_listfield(
931931+ form: Form(widget, output),
932932+ name: String,
933933+ fun: fn(field.Field) -> field.Field,
934934+) -> Form(widget, output) {
935935+ update(form, name, fn(item) {
936936+ case item {
937937+ ListField(field, ..) -> ListField(..item, detail: fun(field))
938938+ _ -> item
939939+ }
940940+ })
941941+}
942942+943943+/// Update the [`SubForm`](https://hexdocs.pm/formz/formz/subform.html) with
944944+/// the given name using the provided function. If multiple subforms have the same
945945+/// name, it will be called on all of them. If no items have the given name,
946946+/// or an item with the given name exists but isn't a `SubForm`, this function
947947+/// will do nothing.
948948+///
949949+/// ```gleam
950950+/// let form = make_form()
951951+/// update(form, "name", subform.set_help_text(_, "..."))
952952+/// ```
633953pub fn update_subform(
634954 form: Form(widget, output),
635955 name: String,
···651971 update(form, name, fn(item) {
652972 case item {
653973 Field(field, state, widget) ->
654654- Field(field, Invalid(state.value, str), widget)
655655- MultiField(..) -> item
656656- SubForm(..) -> item
974974+ Field(field, Invalid(state.value, state.presence, str), widget)
975975+ _ -> item
976976+ }
977977+ })
978978+}
979979+980980+pub fn set_listfield_errors(
981981+ form: Form(widget, output),
982982+ name: String,
983983+ errors: List(Result(Nil, String)),
984984+) -> Form(widget, output) {
985985+ update(form, name, fn(item) {
986986+ case item {
987987+ ListField(field, states, blank_fields, widget) -> {
988988+ let invalid_states =
989989+ list.map2(states, errors, fn(state, err) {
990990+ case err {
991991+ Error(str) -> Invalid(state.value, state.presence, str)
992992+ Ok(Nil) -> state
993993+ }
994994+ })
995995+ ListField(field, invalid_states, blank_fields, widget)
996996+ }
997997+ _ -> item
998998+ }
999999+ })
10001000+}
10011001+10021002+fn get_item_name(item: FormItem(widget)) -> String {
10031003+ case item {
10041004+ Field(field, _, _) -> field.name
10051005+ ListField(field, _, _, _) -> field.name
10061006+ SubForm(subform, _) -> subform.name
10071007+ }
10081008+}
10091009+10101010+/// Create a simple `Definition` that is parsed as an `Option` if the field
10111011+/// is empty.
10121012+pub fn definition(
10131013+ widget widget: widget,
10141014+ parse parse: fn(String) -> Result(required, String),
10151015+ stub stub: required,
10161016+) {
10171017+ Definition(
10181018+ widget:,
10191019+ parse:,
10201020+ stub:,
10211021+ optional_parse: fn(fun, str) {
10221022+ case str {
10231023+ "" -> Ok(option.None)
10241024+ _ -> fun(str) |> result.map(option.Some)
10251025+ }
10261026+ },
10271027+ optional_stub: option.None,
10281028+ )
10291029+}
10301030+10311031+pub fn definition_with_custom_optional(
10321032+ widget widget: widget,
10331033+ parse parse: fn(String) -> Result(required, String),
10341034+ stub stub: required,
10351035+ optional_parse optional_parse: fn(
10361036+ fn(String) -> Result(required, String),
10371037+ String,
10381038+ ) ->
10391039+ Result(optional, String),
10401040+ optional_stub optional_stub: optional,
10411041+) -> Definition(widget, required, optional) {
10421042+ Definition(widget:, parse:, stub:, optional_parse:, optional_stub:)
10431043+}
10441044+10451045+/// Chain additional validation onto the `parse` function. This is
10461046+/// useful if you don't need to change the returned type, but might have
10471047+/// additional constraints. Like say, requiring a `String` to be at least
10481048+/// a certain length, or that an Int must be positive.
10491049+///
10501050+/// ### Example
10511051+/// ```gleam
10521052+/// field
10531053+/// |> validate(fn(i) {
10541054+/// case i > 0 {
10551055+/// True -> Ok(i)
10561056+/// False -> Error("must be positive")
10571057+/// }
10581058+/// }),
10591059+/// ```
10601060+pub fn verify(
10611061+ def: Definition(widget, a, b),
10621062+ fun: fn(a) -> Result(a, String),
10631063+) -> Definition(widget, a, b) {
10641064+ Definition(..def, parse: fn(val) { val |> def.parse |> result.try(fun) })
10651065+}
10661066+10671067+pub fn transform(
10681068+ def: Definition(widget, old_required, old_optional),
10691069+ transform: fn(old_required) -> Result(new_required, String),
10701070+ stub: new_required,
10711071+) -> Definition(widget, new_required, option.Option(new_required)) {
10721072+ let Definition(widget, parse, _, _, _) = def
10731073+ Definition(
10741074+ widget:,
10751075+ parse: fn(val) { val |> parse |> result.try(transform) },
10761076+ stub:,
10771077+ optional_parse: fn(parse, str) {
10781078+ case str {
10791079+ "" -> Ok(option.None)
10801080+ _ -> parse(str) |> result.map(option.Some)
10811081+ }
10821082+ },
10831083+ optional_stub: option.None,
10841084+ )
10851085+}
10861086+10871087+pub fn transform_with_custom_optional(
10881088+ def: Definition(widget, old_required, old_optional),
10891089+ transform: fn(old_required) -> Result(new_required, String),
10901090+ stub: new_required,
10911091+ optional_parse: fn(fn(String) -> Result(new_required, String), String) ->
10921092+ Result(new_optional, String),
10931093+ optional_stub: new_optional,
10941094+) -> Definition(widget, new_required, new_optional) {
10951095+ let Definition(widget, parse, _, _, _) = def
10961096+ Definition(
10971097+ widget:,
10981098+ parse: fn(val) { val |> parse |> result.try(transform) },
10991099+ stub:,
11001100+ optional_parse:,
11011101+ optional_stub:,
11021102+ )
11031103+}
11041104+11051105+pub fn widget(
11061106+ def: Definition(widget, a, b),
11071107+ widget: widget,
11081108+) -> Definition(widget, a, b) {
11091109+ Definition(..def, widget:)
11101110+}
11111111+11121112+@internal
11131113+pub fn get_parse(
11141114+ def: Definition(widget, a, b),
11151115+) -> fn(String) -> Result(a, String) {
11161116+ def.parse
11171117+}
11181118+11191119+@internal
11201120+pub fn get_optional_parse(
11211121+ def: Definition(widget, a, b),
11221122+) -> fn(fn(String) -> Result(a, String), String) -> Result(b, String) {
11231123+ def.optional_parse
11241124+}
11251125+11261126+@internal
11271127+pub fn get_states(form: Form(widget, output)) -> List(FieldState) {
11281128+ form.items |> do_get_states |> list.reverse
11291129+}
11301130+11311131+fn do_get_states(items: List(FormItem(widget))) -> List(FieldState) {
11321132+ list.fold(items, [], fn(acc, item) {
11331133+ case item {
11341134+ Field(_, state, _) -> [state, ..acc]
11351135+ ListField(_, states, _, _) -> list.append(states |> list.reverse, acc)
11361136+ SubForm(_, sub_items) -> list.flatten([do_get_states(sub_items), acc])
6571137 }
6581138 })
6591139}
-152
formz/src/formz/definition.gleam
···11-//// A `Definition` is the second argument needed to add a field to a form. It
22-//// is what describes how a field works, e.g. how it looks and how it's parsed.
33-//// It is the heavy compared to the lightness of a [Field](https://hexdocs.pm/formz/formz/field.html);
44-//// they take a bit more work to make as they are intended to be reusable.
55-////
66-//// The first role of a `Defintion` is to generate the HTML widget for the field.
77-//// This library is format-agnostic and you can generate HTML widgets as raw
88-//// strings, Lustre elements, Nakai nodes, something else, etc. There are
99-//// currently three `formz` libraries that provide common widgets (and field
1010-//// definitions) for the most common HTML formats.
1111-////
1212-//// - [formz_string](https://hexdocs.pm/formz_string/)
1313-//// - [formz_nakai](https://hexdocs.pm/formz_nakai/)
1414-//// - [formz_lustre](https://hexdocs.pm/formz_lustre/) (untested in a browser,
1515-//// would it be useful there??)
1616-////
1717-//// The second role of a `Definition` is to parse the data from the field.
1818-//// There are a two parts to this, as how you parse a field's value depends on
1919-//// if it is optional or required. Not all scenarios can be cookie-cutter
2020-//// placed into an `Option`. So you need to provide two parse functions, one
2121-//// for when a field is required, and a second for when it's optional.
2222-////
2323-//// ### Example password field definition
2424-////
2525-//// ```gleam
2626-//// /// you won't often need to do this directly (I think??). The idea is that
2727-//// /// there'd be libs with the definitions you need.
2828-////
2929-//// import formz/definition.{Definition}
3030-//// import formz/field
3131-//// import formz/validation
3232-//// import formz/widget
3333-//// import lustre/attribute
3434-//// import lustre/element
3535-//// import lustre/element/html
3636-////
3737-//// fn password_widget(
3838-//// field: field.Field,
3939-//// args: widget.Args,
4040-//// ) -> element.Element(msg) {
4141-//// html.input([
4242-//// attribute.type_("password"),
4343-//// attribute.name(field.name),
4444-//// attribute.id(args.id),
4545-//// attribute.attribute("aria-labelledby", field.label),
4646-//// ])
4747-//// }
4848-////
4949-//// pub fn password_field() {
5050-//// Definition(
5151-//// widget: password_widget,
5252-//// parse: validation.string,
5353-//// optional_parse: fn(parse, str) {
5454-//// case str {
5555-//// "" -> Ok(option.None)
5656-//// _ -> parse(str)
5757-//// }
5858-//// },
5959-//// // We need to have a stub value for each parser. The stubs are used when
6060-//// // building the decoder and parse functions for the form.
6161-//// stub: "",
6262-//// optional_stub: option.None,
6363-//// )
6464-//// }
6565-//// ```
6666-6767-import gleam/option
6868-import gleam/result
6969-7070-pub type Definition(widget, required, optional) {
7171- Definition(
7272- /// The widget is used by the form generator to construct the HTML for the
7373- /// field. It's up to the form generator to decide on the widget's type and
7474- /// how it's used.
7575- widget: widget,
7676- /// This parse function takes the raw string from the parsed POST data
7777- /// and converts it to a Gleam type. This `parse` is for when a value
7878- /// is required, so it should return an error if the field is
7979- /// considered empty.
8080- parse: fn(String) -> Result(required, String),
8181- /// The callbacks pattern for generating a form requires a stub
8282- /// value for each field, because the actual decode function is called
8383- /// step by step as the fields are added to the form and `formz` learns
8484- /// the form's details as it goes. This stub value is purely used
8585- /// for navigating the decode function, and just needs to match the type
8686- /// of the real value that can be parsed.
8787- stub: required,
8888- /// If a field is marked as optional, this function is called, with the
8989- /// above parse as an argument. The idea is that this function will
9090- /// call out to the parse function if the field is not empty, and
9191- /// this should only handle the case where the raw input value is empty.
9292- /// This function is necessary because not all fields should just be parsed
9393- /// into an `Option` when they aren't provided.
9494- /// For example, an optional text field might be an empty string,
9595- /// an optional checkbox might be `False`, and an optional select might
9696- /// be `option.None`.
9797- optional_parse: fn(fn(String) -> Result(required, String), String) ->
9898- Result(optional, String),
9999- /// stub for the `optional_parse` return value
100100- optional_stub: optional,
101101- )
102102-}
103103-104104-/// A convenience function to make the simple optional parse function where
105105-/// if a value isn't provided, just return `option.None`, otherwise call out
106106-/// to the parse function and put it's value in `option.Some`.
107107-pub fn make_simple_optional_parse() -> fn(
108108- fn(String) -> Result(required, String),
109109- String,
110110-) ->
111111- Result(option.Option(required), String) {
112112- fn(fun, str) {
113113- case str {
114114- "" -> Ok(option.None)
115115- _ -> fun(str) |> result.map(option.Some)
116116- }
117117- }
118118-}
119119-120120-/// Replace the widget that this `Definition` uses for rendering the field. Most
121121-/// HTML inputs can be interchangeable, they all generate a `String` after all,
122122-/// but not all are the best UX. This allows you to choose the one that is the
123123-/// most appropriate for your field. However, the details of how this works
124124-/// are up to the form generator.
125125-pub fn set_widget(
126126- definition: Definition(widget, a, b),
127127- widget: widget,
128128-) -> Definition(widget, a, b) {
129129- Definition(..definition, widget:)
130130-}
131131-132132-/// Chain additional validation onto the `parse` function. This is
133133-/// useful if you don't need to change the returned type, but might have
134134-/// additional constraints. Like say, requiring a `String` to be at least
135135-/// a certain length, or that an Int must be positive.
136136-///
137137-/// ### Example
138138-/// ```gleam
139139-/// field
140140-/// |> validate(fn(i) {
141141-/// case i > 0 {
142142-/// True -> Ok(i)
143143-/// False -> Error("must be positive")
144144-/// }
145145-/// }),
146146-/// ```
147147-pub fn validate(
148148- def: Definition(widget, a, b),
149149- fun: fn(a) -> Result(a, String),
150150-) -> Definition(widget, a, b) {
151151- Definition(..def, parse: fn(val) { val |> def.parse |> result.try(fun) })
152152-}
-12
formz/src/formz/field.gleam
···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- /// Whether the field is required. This field is not functional, but is purely
4141- /// for whether or not the form generator should indicate to the user that the
4242- /// field is required. Add the field to the form with either `optional` or
4343- /// `required` methods to control this functionally. Those methods will make
4444- /// sure this field is set correctly.
4545- required: Bool,
4640 /// Whether the field is hidden. A hidden field is not displayed in the browser.
4741 hidden: Bool,
4842 )
···6155 label: justin.sentence_case(name),
6256 help_text: "",
6357 disabled: False,
6464- required: False,
6558 hidden: False,
6659 )
6760}
···80738174pub fn set_hidden(field: Field, hidden: Bool) -> Field {
8275 Field(..field, hidden:)
8383-}
8484-8585-@internal
8686-pub fn set_required(field: Field, required: Bool) -> Field {
8787- Field(..field, required:)
8876}
89779078pub fn make_hidden(field: Field) -> Field {
+1-1
formz/src/formz/subform.gleam
···11-//// Detials about a subform being added to a form. There is a convenience
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
+1-1
formz/src/formz/validation.gleam
···122122}
123123124124/// Validates that the input is one from a list of allowed values. Takes a list
125125-/// of Gleam values that can be chosen. This uses the index of the item in to
125125+/// of Gleam values that can be chosen. This uses the index of the item to
126126/// find the desired value.
127127///
128128/// ## Examples
···11+//// The goal of a "widget" in `formz` is to produce an HTML input like
22+//// `<input>`, `<select>`, or `<textarea>`. In a [`Definition`](https://hexdocs.pm/formz/formz/definition.html),
33+//// a widget can be any Gleam type, and it's up to the form generator being
44+//// used to know the exact type you need.
55+////
66+//// That said, in the bundled form generators a widget is a function that
77+//// takes the details of a field and some render time arguments that the form
88+//// generator needs to construct an input. This module is for those form
99+//// generators, and it's use is optional if you have different needs.
1010+1111+import formz
1212+import formz/field
1313+import lustre/element
1414+1515+pub type Widget(msg) =
1616+ fn(field.Field, formz.FieldState, Args) -> element.Element(msg)
1717+1818+pub type Args {
1919+ Args(
2020+ /// The id of the input element.
2121+ id: String,
2222+ /// Details of how the input is labelled. Some sort of label is required for accessibility.
2323+ labelled_by: LabelledBy,
2424+ /// Details of how the input is described. This is optional, but can be useful for accessibility.
2525+ described_by: DescribedBy,
2626+ )
2727+}
2828+2929+pub type LabelledBy {
3030+ /// The input is labelled by a `<label>` element with a `for` attribute
3131+ /// pointing to this input's id. This has the best accessibility support
3232+ /// and should be [preferred when possible](https://www.w3.org/WAI/tutorials/forms/labels/).
3333+ LabelledByLabelFor
3434+ /// The input should be labelled using the `Field`'s `label` field.
3535+ LabelledByFieldValue
3636+ /// The input is labelled by elements with the specified ids.
3737+ LabelledByElementsWithIds(ids: List(String))
3838+}
3939+4040+pub type DescribedBy {
4141+ /// The input is described by elements with the specified ids. This is useful
4242+ /// for additional instructions or error messages.
4343+ DescribedByElementsWithIds(ids: List(String))
4444+ DescribedByNone
4545+}
+43-27
formz_lustre/src/formz_lustre/widgets.gleam
···11+import formz
12import formz/field.{type Field}
22-import formz/widget
33+import formz_lustre/widget
34import gleam/list
45import gleam/string
56import lustre/attribute
···6162 }
6263}
63646464-fn required_attr(requried: Bool) -> attribute.Attribute(msg) {
6565- // case requried {
6666- // True -> attribute.required(True)
6767- // False -> attribute.none()
6868- // }
6969- attribute.required(requried)
6565+fn required_attr(presence: formz.FieldPresence) -> attribute.Attribute(msg) {
6666+ case presence {
6767+ formz.Required -> attribute.required(True)
6868+ formz.Optional -> attribute.none()
6969+ }
7070}
71717272fn step_size_attr(step_size: String) -> attribute.Attribute(msg) {
···9393/// Create an `<input type="checkbox">`. The checkbox is checked
9494/// if the value is "on" (the browser default).
9595pub fn checkbox_widget() {
9696- fn(field: Field, args: widget.Args) {
9797- do_input_widget(field |> field.set_raw_value(""), args, "checkbox", [
9898- checked_attr(field.value),
9999- ])
9696+ fn(field: Field, state: formz.FieldState, args: widget.Args) {
9797+ let value = state.value
9898+ let state = case state {
9999+ formz.Unvalidated(_, presence) -> formz.Unvalidated("", presence)
100100+ formz.Valid(_, presence) -> formz.Valid("", presence)
101101+ formz.Invalid(_, presence, e) -> formz.Invalid("", presence, e)
102102+ }
103103+ do_input_widget(field, state, args, "checkbox", [checked_attr(value)])
100104 }
101105}
102106···107111/// the step size. If you truly need any float, then a `type="text"` input might be a
108112/// better choice.
109113pub fn number_widget(step_size: String) {
110110- fn(field: Field, args: widget.Args) {
111111- do_input_widget(field, args, "number", [step_size_attr(step_size)])
114114+ fn(field: Field, state: formz.FieldState, args: widget.Args) {
115115+ do_input_widget(field, state, args, "number", [step_size_attr(step_size)])
112116 }
113117}
114118115119/// Create an `<input type="password">`. This will not output the value in the
116120/// generated HTML for privacy/security concerns.
117121pub fn password_widget() {
118118- fn(field: Field, args: widget.Args) {
119119- do_input_widget(field |> field.set_raw_value(""), args, "password", [])
122122+ fn(field: Field, state: formz.FieldState, args: widget.Args) {
123123+ let state = case state {
124124+ formz.Unvalidated(_, presence) -> formz.Unvalidated("", presence)
125125+ formz.Valid(_, presence) -> formz.Valid("", presence)
126126+ formz.Invalid(_, presence, e) -> formz.Invalid("", presence, e)
127127+ }
128128+ do_input_widget(field, state, args, "password", [])
120129 }
121130}
122131123132/// Generate any `<input>` like `type="text"`, `type="email"` or
124133/// `type="url"`.
125134pub fn input_widget(type_: String) {
126126- fn(field: Field, args: widget.Args) {
127127- do_input_widget(field, args, type_, [])
135135+ fn(field: Field, state: formz.FieldState, args: widget.Args) {
136136+ do_input_widget(field, state, args, type_, [])
128137 }
129138}
130139131140fn do_input_widget(
132141 field: Field,
142142+ state: formz.FieldState,
133143 args: widget.Args,
134144 type_: String,
135145 extra_attrs: List(attribute.Attribute(msg)),
···140150 attribute.type_(type_),
141151 name_attr(field.name),
142152 id_attr(args.id),
143143- required_attr(field.required),
153153+ required_attr(state.presence),
144154 disabled_attr(field.disabled),
145145- value_attr(field.value),
155155+ value_attr(state.value),
146156 aria_label_attr(args.labelled_by, field.label),
147157 aria_describedby_attr(args.described_by),
148158 ],
···153163154164/// Create a `<textarea></textarea>`.
155165pub fn textarea_widget() {
156156- fn(field: Field, args: widget.Args) -> element.Element(msg) {
166166+ fn(field: Field, state: formz.FieldState, args: widget.Args) -> element.Element(
167167+ msg,
168168+ ) {
157169 html.textarea(
158170 [
159171 name_attr(field.name),
160172 id_attr(args.id),
161161- required_attr(field.required),
173173+ required_attr(state.presence),
162174 aria_label_attr(args.labelled_by, field.label),
163175 aria_describedby_attr(args.described_by),
164176 ],
165165- field.value,
177177+ state.value,
166178 )
167179 }
168180}
···171183/// passing data around and you don't want it to be visible to the user. Like
172184/// say, the ID of a record being edited.
173185pub fn hidden_widget() {
174174- fn(field: Field, _args: widget.Args) -> element.Element(msg) {
186186+ fn(field: Field, state: formz.FieldState, _args: widget.Args) -> element.Element(
187187+ msg,
188188+ ) {
175189 html.input([
176190 attribute.type_("hidden"),
177191 name_attr(field.name),
178178- value_attr(field.value),
192192+ value_attr(state.value),
179193 ])
180194 }
181195}
···184198/// of variants is a two-tuple, where the first item is the text to display and
185199/// the second item is the value.
186200pub fn select_widget(variants: List(#(String, String))) {
187187- fn(field: Field, args: widget.Args) -> element.Element(msg) {
201201+ fn(field: Field, state: formz.FieldState, args: widget.Args) -> element.Element(
202202+ msg,
203203+ ) {
188204 html.select(
189205 [
190206 name_attr(field.name),
191207 id_attr(args.id),
192192- required_attr(field.required),
208208+ required_attr(state.presence),
193209 aria_label_attr(args.labelled_by, field.label),
194210 aria_describedby_attr(args.described_by),
195211 ],
···198214 list.map(variants, fn(variant) {
199215 let val = variant.1
200216 html.option(
201201- [attribute.value(val), attribute.selected(field.value == val)],
217217+ [attribute.value(val), attribute.selected(state.value == val)],
202218 variant.0,
203219 )
204220 }),
···11+//// The goal of a "widget" in `formz` is to produce an HTML input like
22+//// `<input>`, `<select>`, or `<textarea>`. In a [`Definition`](https://hexdocs.pm/formz/formz/definition.html),
33+//// a widget can be any Gleam type, and it's up to the form generator being
44+//// used to know the exact type you need.
55+////
66+//// That said, in the bundled form generators a widget is a function that
77+//// takes the details of a field and some render time arguments that the form
88+//// generator needs to construct an input. This module is for those form
99+//// generators, and it's use is optional if you have different needs.
1010+1111+import formz
1212+import formz/field
1313+import nakai/html
1414+1515+pub type Widget =
1616+ fn(field.Field, formz.FieldState, Args) -> html.Node
1717+1818+pub type Args {
1919+ Args(
2020+ /// The id of the input element.
2121+ id: String,
2222+ /// Details of how the input is labelled. Some sort of label is required for accessibility.
2323+ labelled_by: LabelledBy,
2424+ /// Details of how the input is described. This is optional, but can be useful for accessibility.
2525+ described_by: DescribedBy,
2626+ )
2727+}
2828+2929+pub type LabelledBy {
3030+ /// The input is labelled by a `<label>` element with a `for` attribute
3131+ /// pointing to this input's id. This has the best accessibility support
3232+ /// and should be [preferred when possible](https://www.w3.org/WAI/tutorials/forms/labels/).
3333+ LabelledByLabelFor
3434+ /// The input should be labelled using the `Field`'s `label` field.
3535+ LabelledByFieldValue
3636+ /// The input is labelled by elements with the specified ids.
3737+ LabelledByElementsWithIds(ids: List(String))
3838+}
3939+4040+pub type DescribedBy {
4141+ /// The input is described by elements with the specified ids. This is useful
4242+ /// for additional instructions or error messages.
4343+ DescribedByElementsWithIds(ids: List(String))
4444+ DescribedByNone
4545+}
+36-25
formz_nakai/src/formz_nakai/widgets.gleam
···11+import formz
12import formz/field.{type Field}
22-import formz/widget
33+import formz_nakai/widget
34import gleam/string
4556import gleam/list
···6162 }
6263}
63646464-fn required_attr(required: Bool) -> List(attr.Attr) {
6565- case required {
6666- True -> [attr.required("")]
6767- False -> []
6565+fn required_attr(presence: formz.FieldPresence) -> List(attr.Attr) {
6666+ case presence {
6767+ formz.Required -> [attr.required("")]
6868+ formz.Optional -> []
6869 }
6970}
7071···9293/// Create an `<input type="checkbox">`. The checkbox is checked
9394/// if the value is "on" (the browser default).
9495pub fn checkbox_widget() {
9595- fn(field: Field, args: widget.Args) {
9696- do_input_widget(field |> field.set_raw_value(""), args, "checkbox", [
9797- checked_attr(field.value),
9898- ])
9696+ fn(field: Field, state: formz.FieldState, args: widget.Args) {
9797+ let value = state.value
9898+ let state = case state {
9999+ formz.Unvalidated(_, presence) -> formz.Unvalidated("", presence)
100100+ formz.Valid(_, presence) -> formz.Valid("", presence)
101101+ formz.Invalid(_, presence, e) -> formz.Invalid("", presence, e)
102102+ }
103103+ do_input_widget(field, state, args, "checkbox", [checked_attr(value)])
99104 }
100105}
101106···106111/// the step size. If you truly need any float, then a `type="text"` input might be a
107112/// better choice.
108113pub fn number_widget(step_size: String) {
109109- fn(field: Field, args: widget.Args) {
110110- do_input_widget(field, args, "number", [step_size_attr(step_size)])
114114+ fn(field: Field, state: formz.FieldState, args: widget.Args) {
115115+ do_input_widget(field, state, args, "number", [step_size_attr(step_size)])
111116 }
112117}
113118114119/// Create an `<input type="password">`. This will not output the value in the
115120/// generated HTML for privacy/security concerns.
116121pub fn password_widget() {
117117- fn(field: Field, args: widget.Args) {
118118- do_input_widget(field |> field.set_raw_value(""), args, "password", [])
122122+ fn(field: Field, state: formz.FieldState, args: widget.Args) {
123123+ let state = case state {
124124+ formz.Unvalidated(_, presence) -> formz.Unvalidated("", presence)
125125+ formz.Valid(_, presence) -> formz.Valid("", presence)
126126+ formz.Invalid(_, presence, e) -> formz.Invalid("", presence, e)
127127+ }
128128+ do_input_widget(field, state, args, "password", [])
119129 }
120130}
121131122132/// Generate any `<input>` like `type="text"`, `type="email"` or
123133/// `type="url"`.
124134pub fn input_widget(type_: String) {
125125- fn(field: Field, args: widget.Args) {
126126- do_input_widget(field, args, type_, [])
135135+ fn(field: Field, state: formz.FieldState, args: widget.Args) {
136136+ do_input_widget(field, state, args, type_, [])
127137 }
128138}
129139130140fn do_input_widget(
131141 field: Field,
142142+ state: formz.FieldState,
132143 args: widget.Args,
133144 type_: String,
134145 extra_attrs: List(List(attr.Attr)),
···138149 type_attr(type_),
139150 name_attr(field.name),
140151 id_attr(args.id),
141141- required_attr(field.required),
142142- value_attr(field.value),
152152+ required_attr(state.presence),
153153+ value_attr(state.value),
143154 disabled_attr(field.disabled),
144155 aria_describedby_attr(args.described_by),
145156 aria_label_attr(args.labelled_by, field.label),
···150161151162/// Create a `<textarea></textarea>`.
152163pub fn textarea_widget() {
153153- fn(field: Field, args: widget.Args) -> html.Node {
164164+ fn(field: Field, state: formz.FieldState, args: widget.Args) -> html.Node {
154165 html.textarea(
155166 list.flatten([
156167 name_attr(field.name),
157168 id_attr(args.id),
158158- required_attr(field.required),
169169+ required_attr(state.presence),
159170 aria_label_attr(args.labelled_by, field.label),
160171 ]),
161161- [html.Text(field.value)],
172172+ [html.Text(state.value)],
162173 )
163174 }
164175}
···167178/// passing data around and you don't want it to be visible to the user. Like
168179/// say, the ID of a record being edited.
169180pub fn hidden_widget() {
170170- fn(field: Field, _) -> html.Node {
181181+ fn(field: Field, state: formz.FieldState, _) -> html.Node {
171182 html.input(
172183 list.flatten([
173184 type_attr("hidden"),
174185 name_attr(field.name),
175175- value_attr(field.value),
186186+ value_attr(state.value),
176187 ]),
177188 )
178189 }
···182193/// of variants is a two-tuple, where the first item is the text to display and
183194/// the second item is the value.
184195pub fn select_widget(variants: List(#(String, String))) {
185185- fn(field: Field, args: widget.Args) -> html.Node {
196196+ fn(field: Field, state: formz.FieldState, args: widget.Args) -> html.Node {
186197 html.select(
187198 list.flatten([
188199 name_attr(field.name),
189200 id_attr(args.id),
190190- required_attr(field.required),
201201+ required_attr(state.presence),
191202 aria_label_attr(args.labelled_by, field.label),
192203 ]),
193204 list.flatten([
194205 [html.option([attr.value("")], [html.Text("Select...")]), html.hr([])],
195206 list.map(variants, fn(variant) {
196207 let val = variant.1
197197- let selected_attr = case field.value == val {
208208+ let selected_attr = case state.value == val {
198209 True -> [attr.selected()]
199210 _ -> []
200211 }