···105105The second role is to parse the data from the field. There are a two parts
106106to this, as how you parse a field's value depends on if it is optional or
107107required. For example, an optional text field might be an empty string,
108108-an optional checkbox might be `false`, and an optional select might
108108+an optional checkbox might be `False`, and an optional select might
109109be `option.None`. So you need to provide two parse functions, one for when
110110a field is required, and a second for when it's optional (and it uses the first
111111one).
+123-13
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 `Definition` 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, etc. There are
99+//// currently three formz libraries that provide common field definitions in
1010+//// different 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
1919+//// to this, as how you parse a field's value depends on if it is optional or
2020+//// required. Not all scenarios can be cookie-cutter placed into an `Option`.
2121+//// So you need to provide two parse functions, one for when
2222+//// a field is required, and a second for when it's optional.
2323+////
2424+//// ### Example password field definition
2525+////
2626+//// ```gleam
2727+//// /// you won't often need to do this directly (I think??). The idea is that
2828+//// /// there'd be libs with the definitions you need.
2929+////
3030+//// import formz/definition.{Definition}
3131+//// import formz/field
3232+//// import formz/validation
3333+//// import formz/widget
3434+//// import lustre/attribute
3535+//// import lustre/element
3636+//// import lustre/element/html
3737+////
3838+//// fn password_widget(
3939+//// field: field.Field,
4040+//// args: widget.Args,
4141+//// ) -> element.Element(msg) {
4242+//// html.input([
4343+//// attribute.type_("password"),
4444+//// attribute.name(field.name),
4545+//// attribute.id(args.id),
4646+//// attribute.attribute("aria-labelledby", field.label),
4747+//// ])
4848+//// }
4949+////
5050+//// pub fn password_field() {
5151+//// Definition(
5252+//// widget: password_widget,
5353+//// parse: validation.string,
5454+//// optional_parse: fn(parse, str) {
5555+//// case str {
5656+//// "" -> Ok(option.None)
5757+//// _ -> parse(str)
5858+//// }
5959+//// },
6060+//// // We need to have a stub value for each parser that's used
6161+//// // when building the decoder and parse functions for the form as the fields
6262+//// // are being added
6363+//// stub: "",
6464+//// optional_stub: option.None,
6565+//// )
6666+//// }
6767+//// ```
6868+169import formz/widget
270import gleam/option
371import gleam/result
472573pub type Definition(format, required, optional) {
674 Definition(
7575+ /// The widget generates the HTML for the field.
776 widget: widget.Widget(format),
7777+ /// This parse function takes the raw string from the parsed POST data
7878+ /// and converts it to a Gleam type. This `parse` is for when a value
7979+ /// is required, so it should return an error if the field is empty.
880 parse: fn(String) -> Result(required, String),
8181+ /// The `use`/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.
987 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`.
1097 optional_parse: fn(fn(String) -> Result(required, String), String) ->
1198 Result(optional, String),
9999+ /// stub for the optional_parse return value
12100 optional_stub: optional,
13101 )
14102}
15103104104+/// 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.
16124pub fn set_widget(
17125 definition: Definition(format, a, b),
18126 widget: widget.Widget(format),
···20128 Definition(..definition, widget:)
21129}
22130131131+/// Chain additional validation onto the `parse` function. This is
132132+/// useful if you don't need to change the returned type, but might have
133133+/// additional constraints. Like say, requiring a `String` to be at least
134134+/// a certain length, or that an Int must be positive.
135135+///
136136+/// ### Example
137137+/// ```gleam
138138+/// field
139139+/// |> validate(fn(i) {
140140+/// case i > 0 {
141141+/// True -> Ok(i)
142142+/// False -> Error("must be positive")
143143+/// }
144144+/// }),
145145+/// ```
23146pub fn validate(
24147 def: Definition(format, a, b),
25148 fun: fn(a) -> Result(a, String),
26149) -> Definition(format, a, b) {
27150 Definition(..def, parse: fn(val) { val |> def.parse |> result.try(fun) })
28151}
2929-3030-pub fn make_simple_optional_parse() -> fn(
3131- fn(String) -> Result(required, String),
3232- String,
3333-) ->
3434- Result(option.Option(required), String) {
3535- fn(fun, str) {
3636- case str {
3737- "" -> Ok(option.None)
3838- _ -> fun(str) |> result.map(option.Some)
3939- }
4040- }
4141-}
+4
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.
44+15import justin
2637pub type Field {
+2-2
formz_lustre/src/formz_lustre/definitions.gleam
···1515/// Create a basic form input. Parsed as a String.
1616pub fn text_field() {
1717 Definition(
1818- widgets.text_like_widget("text"),
1818+ widgets.input_widget("text"),
1919 validation.non_empty_string,
2020 "",
2121 fn(fun, str) {
···3232/// look like an email address, i.e. the string has an `@`.
3333pub fn email_field() {
3434 Definition(
3535- widgets.text_like_widget("email"),
3535+ widgets.input_widget("email"),
3636 validation.email,
3737 "",
3838 definition.make_simple_optional_parse(),
+20-3
formz_lustre/src/formz_lustre/widgets.gleam
···8282 }
8383}
84848585-// Create a checkbox widget (`<input type="checkbox">`). The checkbox is checked
8686-// if the value is "on" (the browser default).
8585+/// Create an `<input type="checkbox">`. The checkbox is checked
8686+/// if the value is "on" (the browser default).
8787pub fn checkbox_widget() {
8888 fn(field: Field, args: widget.Args) {
8989 do_input_widget(field |> field.set_raw_value(""), args, "checkbox", [
···9292 }
9393}
94949595+/// Create a `<input type="number">`. Normally browsers only allow whole numbers,
9696+/// unless a decimal step size is provided. The step size here is a string that
9797+/// will be put straight into the `step-size` attribute. Doing non-whole numbers
9898+/// this way does mean that a user can only input numbers up to the precision of
9999+/// the step size. If you truly need any float, then a `type="text"` input might be a
100100+/// better choice.
95101pub fn number_widget(step_size: String) {
96102 fn(field: Field, args: widget.Args) {
97103 do_input_widget(field, args, "number", [step_size_attr(step_size)])
98104 }
99105}
100106107107+/// Create an `<input type="password">`. This will not output the value in the
108108+/// generated HTML for privacy/security concerns.
101109pub fn password_widget() {
102110 fn(field: Field, args: widget.Args) {
103111 do_input_widget(field |> field.set_raw_value(""), args, "password", [])
104112 }
105113}
106114107107-pub fn text_like_widget(type_: String) {
115115+/// Generate any `<input>` like `type="text"`, `type="email"` or
116116+/// `type="url"`.
117117+pub fn input_widget(type_: String) {
108118 fn(field: Field, args: widget.Args) {
109119 do_input_widget(field, args, type_, [])
110120 }
···133143 )
134144}
135145146146+/// Create a `<textarea></textarea>`.
136147pub fn textarea_widget() {
137148 fn(field: Field, args: widget.Args) -> element.Element(msg) {
138149 html.textarea(
···148159 }
149160}
150161162162+/// Create a `<input type="hidden">`. This is useful for if a field is just
163163+/// passing data around and you don't want it to be visible to the user. Like
164164+/// say, the ID of a record being edited.
151165pub fn hidden_widget() {
152166 fn(field: Field, _args: widget.Args) -> element.Element(msg) {
153167 html.input([
···158172 }
159173}
160174175175+/// Create a `<select></select>` with `<option>`s for each variant. The list
176176+/// of variants is a two-tuple, where the first item is the text to display and
177177+/// the second item is the value.
161178pub fn select_widget(variants: List(#(String, String))) {
162179 fn(field: Field, args: widget.Args) -> element.Element(msg) {
163180 html.select(
···1515/// Create a basic form input. Parsed as a String.
1616pub fn text_field() {
1717 Definition(
1818- widgets.text_like_widget("text"),
1818+ widgets.input_widget("text"),
1919 validation.non_empty_string,
2020 "",
2121 fn(fun, str) {
···3232/// look like an email address, i.e. the string has an `@`.
3333pub fn email_field() {
3434 Definition(
3535- widgets.text_like_widget("email"),
3535+ widgets.input_widget("email"),
3636 validation.email,
3737 "",
3838 definition.make_simple_optional_parse(),
+20-3
formz_nakai/src/formz_nakai/widgets.gleam
···8585 }
8686}
87878888-// Create a checkbox widget (`<input type="checkbox">`). The checkbox is checked
8989-// if the value is "on" (the browser default).
8888+/// Create an `<input type="checkbox">`. The checkbox is checked
8989+/// if the value is "on" (the browser default).
9090pub fn checkbox_widget() {
9191 fn(field: Field, args: widget.Args) {
9292 do_input_widget(field |> field.set_raw_value(""), args, "checkbox", [
···9595 }
9696}
97979898+/// Create a `<input type="number">`. Normally browsers only allow whole numbers,
9999+/// unless a decimal step size is provided. The step size here is a string that
100100+/// will be put straight into the `step-size` attribute. Doing non-whole numbers
101101+/// this way does mean that a user can only input numbers up to the precision of
102102+/// the step size. If you truly need any float, then a `type="text"` input might be a
103103+/// better choice.
98104pub fn number_widget(step_size: String) {
99105 fn(field: Field, args: widget.Args) {
100106 do_input_widget(field, args, "number", [step_size_attr(step_size)])
101107 }
102108}
103109110110+/// Create an `<input type="password">`. This will not output the value in the
111111+/// generated HTML for privacy/security concerns.
104112pub fn password_widget() {
105113 fn(field: Field, args: widget.Args) {
106114 do_input_widget(field |> field.set_raw_value(""), args, "password", [])
107115 }
108116}
109117110110-pub fn text_like_widget(type_: String) {
118118+/// Generate any `<input>` like `type="text"`, `type="email"` or
119119+/// `type="url"`.
120120+pub fn input_widget(type_: String) {
111121 fn(field: Field, args: widget.Args) {
112122 do_input_widget(field, args, type_, [])
113123 }
···134144 )
135145}
136146147147+/// Create a `<textarea></textarea>`.
137148pub fn textarea_widget() {
138149 fn(field: Field, args: widget.Args) -> html.Node {
139150 html.textarea(
···148159 }
149160}
150161162162+/// Create a `<input type="hidden">`. This is useful for if a field is just
163163+/// passing data around and you don't want it to be visible to the user. Like
164164+/// say, the ID of a record being edited.
151165pub fn hidden_widget() {
152166 fn(field: Field, _) -> html.Node {
153167 html.input(
···160174 }
161175}
162176177177+/// Create a `<select></select>` with `<option>`s for each variant. The list
178178+/// of variants is a two-tuple, where the first item is the text to display and
179179+/// the second item is the value.
163180pub fn select_widget(variants: List(#(String, String))) {
164181 fn(field: Field, args: widget.Args) -> html.Node {
165182 html.select(
···1515/// Create a basic form input. Parsed as a String.
1616pub fn text_field() {
1717 Definition(
1818- widgets.text_like_widget("text"),
1818+ widgets.input_widget("text"),
1919 validation.non_empty_string,
2020 "",
2121 fn(fun, str) {
···3232/// look like an email address, i.e. the string has an `@`.
3333pub fn email_field() {
3434 Definition(
3535- widgets.text_like_widget("email"),
3535+ widgets.input_widget("email"),
3636 validation.email,
3737 "",
3838 definition.make_simple_optional_parse(),
···6262 )
6363}
64646565-/// Create a checkbox form input. Parsed as a Boolean.
6565+/// Create a checkbox form input. Parsed as a `Bool`. If required, the parsed
6666+/// `Bool` must be `True`.
6667pub fn boolean_field() {
6768 definition.Definition(
6869 widget: widgets.checkbox_widget(),
···7879 )
7980}
80818181-/// Create a password form input, which hides the input value. Parsed as a String
8282+/// Create a password form input, which hides the input value. Parsed as a String.
8283pub fn password_field() {
8384 Definition(
8485 widgets.password_widget(),
···91929293/// Creates a `<select>` input. Takes a tuple of #(String, String) where the first
9394/// item in the tuple is the label, and the second item can be any Gleam type and
9494-/// is the value that would be parsed for a given selection.
9595+/// is the value that would be parsed for a given selection. The actual values
9696+/// rendered in the `<option>` tags are the numeric indeces of the items in the
9797+/// list.
9598///
9696-/// Because of how you build `formz` forms, you need to provide a placeholder of
9999+/// Because of how you build `formz` forms, you need to provide a stub of
97100/// the value type. Is this annoying? Would it be more or less annoying if I
98101/// required a non-empty list for the variants instead? I'm not sure. Let me know!
99102pub fn choices_field(variants: List(#(String, enum)), stub stub: enum) {
+20-3
formz_string/src/formz_string/widgets.gleam
···9494 }
9595}
96969797-// Create a checkbox widget (`<input type="checkbox">`). The checkbox is checked
9898-// if the value is "on" (the browser default).
9797+/// Create an `<input type="checkbox">`. The checkbox is checked
9898+/// if the value is "on" (the browser default).
9999pub fn checkbox_widget() {
100100 fn(field: Field, args: widget.Args) {
101101 do_input_widget(field |> field.set_raw_value(""), args, "checkbox", [
···104104 }
105105}
106106107107+/// Create a `<input type="number">`. Normally browsers only allow whole numbers,
108108+/// unless a decimal step size is provided. The step size here is a string that
109109+/// will be put straight into the `step-size` attribute. Doing non-whole numbers
110110+/// this way does mean that a user can only input numbers up to the precision of
111111+/// the step size. If you truly need any float, then a `type="text"` input might be a
112112+/// better choice.
107113pub fn number_widget(step_size: String) {
108114 fn(field: Field, args: widget.Args) {
109115 do_input_widget(field, args, "number", [step_size_attr(step_size)])
110116 }
111117}
112118119119+/// Create an `<input type="password">`. This will not output the value in the
120120+/// generated HTML for privacy/security concerns.
113121pub fn password_widget() {
114122 fn(field: Field, args: widget.Args) {
115123 do_input_widget(field |> field.set_raw_value(""), args, "password", [])
116124 }
117125}
118126119119-pub fn text_like_widget(type_: String) {
127127+/// Generate any `<input>` like `type="text"`, `type="email"` or
128128+/// `type="url"`.
129129+pub fn input_widget(type_: String) {
120130 fn(field: Field, args: widget.Args) {
121131 do_input_widget(field, args, type_, [])
122132 }
···141151 <> ">"
142152}
143153154154+/// Create a `<textarea></textarea>`.
144155pub fn textarea_widget() {
145156 fn(field: Field, args: widget.Args) -> String {
146157 // https://chriscoyier.net/2023/09/29/css-solves-auto-expanding-textareas-probably-eventually/
···158169 }
159170}
160171172172+/// Create a `<input type="hidden">`. This is useful for if a field is just
173173+/// passing data around and you don't want it to be visible to the user. Like
174174+/// say, the ID of a record being edited.
161175pub fn hidden_widget() {
162176 fn(field: Field, _args: widget.Args) -> String {
163177 "<input"
···168182 }
169183}
170184185185+/// Create a `<select></select>` with `<option>`s for each variant. The list
186186+/// of variants is a two-tuple, where the first item is the text to display and
187187+/// the second item is the value.
171188pub fn select_widget(variants: List(#(String, String))) {
172189 fn(field: Field, args: widget.Args) {
173190 let choices =