this repo has no description
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

remove `field` and `subform` modules, use `Config` in main module

+1285 -916
+41 -62
README.md
··· 11 11 to make that link explicit and easy to manage, while making it really easy 12 12 to make accessible forms. 13 13 14 - Note: This library is not particularly well-suited for generating one-off 15 - forms, but is more intended for use in projects where you have a few forms to 14 + Note: This library is not necessarily well-suited for generating one-off 15 + forms, and is intended for use in projects where you have a few forms to 16 16 manage, and would like to keep the form markup and parsing logic in sync. It 17 17 takes some amount of effort to make an actual form generator with markup and 18 - styles, and that might not be worth it for a one-off form. That said, a simple 18 + styles, and that might not be worth it for a one-off form. That said, a simple 19 19 form generator is provided if you aren't opinionated about your markup. 20 20 21 21 ```sh ··· 29 29 30 30 ```gleam 31 31 import formz 32 - import formz/field.{field} 33 32 import formz_string/definitions 34 33 35 34 pub fn make_form() { 36 - use username <- formz.field( 37 - formz.named("username"), 38 - formz.required(definitions.text_field()), 39 - ) 40 - use password <- formz.field( 41 - formz.named("password"), 42 - formz.required(definitions.password_field()), 43 - ) 35 + use username <- formz.field(formz.named("username"), definitions.text_field()) 36 + use password <- formz.field(formz.named("password"), definitions.password_field()) 44 37 45 38 formz.create_form(#(username, password)) 46 39 } ··· 50 43 51 44 There are two arguments to adding a field to a form (seen above): 52 45 53 - 1. A [Field](https://hexdocs.pm/formz/formz/field.html), which holds specific, 54 - unique details about the field, such as its name, label, help text, disabled 55 - state, etc. 56 - 2. A [Definition](https://hexdocs.pm/formz/formz/definition.html), which 57 - says (A) how to generate the HTML input element for the field, and (B) how 58 - to parse the data from the field. These definitions are reusable and can be 59 - shared across fields, forms and projects. 46 + 1. A `Config`, which holds specific, unique details about the field: its name, 47 + label, help text, and disabled state. 48 + 2. A `Definition`, which says (A) how to generate the HTML input element for 49 + the field, and (B) how to parse the data from the field. These definitions 50 + are reusable and can be shared across fields, forms and projects. 60 51 61 - ### Field details 52 + ### Field config 62 53 63 54 ```gleam 64 - // name is required, the other details are optional 65 - field(named: "username") 66 - |> field.set_label("Username") 67 - |> field.set_help_text("Only alphanumeric characters are allowed.") 55 + // name is required, the other confg are optional 56 + formz.named("username") 57 + |> formz.set_label("Username") 58 + |> formz.set_help_text("Only alphanumeric characters are allowed.") 68 59 ``` 69 60 70 61 ```gleam 71 - field(named: "userid") |> field.make_hidden |> field.set_raw_value("42") 62 + formz.named("userid") |> formz.make_disabled 72 63 ``` 73 64 74 65 ### Field definition 75 66 76 - [Defintions](https://hexdocs.pm/formz/formz/definition.html) are the heavy 77 - compared to the lightness of fields; they take a bit more work to make as they 78 - are intended to be more reusable. 67 + A `Definition` describes how an input works, e.g. how it looks and how it's 68 + parsed. Definitions are intended to be reusable. 69 + 70 + The first role of a `Defintion` is to generate the HTML input for the field. 71 + This library is format-agnostic and you can generate inputs as raw 72 + strings, Lustre elements, Nakai nodes, something else, etc. The second role 73 + of a `Definition` is to parse the raw string data from the input into a 74 + Gleam type. 79 75 80 - The first role of a `Defintion` is to generate the HTML widget for the field. 81 - This library is format-agnostic and you can generate HTML widgets as raw 82 - strings, Lustre elements, Nakai nodes, something else, etc. There are 83 - currently three `formz` libraries that provide common field definitions for the 84 - most common HTML formats. 76 + There are currently three `formz` libraries that provide common field 77 + definitions for the most common HTML inputs: 85 78 86 79 - [formz_string](https://hexdocs.pm/formz_string/) 87 80 - [formz_nakai](https://hexdocs.pm/formz_nakai/) 88 - - [formz_lustre](https://hexdocs.pm/formz_lustre/) (untested in a browser, I've only done this server side) 89 - 90 - The second role of a `Definition` is to parse the data from the field. There 91 - are a two parts to this, as how you parse a field's value depends on if it is 92 - optional or required. Not all scenarios can be cookie-cutter placed into an 93 - `Option`. So you need to provide two parse functions, one for when a field is 94 - required, and a second for when it's optional. 81 + - [formz_lustre](https://hexdocs.pm/formz_lustre/) 95 82 96 83 ```gleam 97 84 /// you won't often need to do this directly (I think??). The idea is that 98 85 /// there'd be libs with the definitions you need. 99 86 100 - import formz/definition.{Definition} 101 - import formz/field 87 + import formz 102 88 import formz/validation 103 - import formz/widget 104 89 import lustre/attribute 105 90 import lustre/element 106 91 import lustre/element/html 107 92 108 93 fn password_widget( 109 - field: field.Field, 110 - args: widget.Args, 94 + config: formz.Config, 95 + state: formz.InputState, 111 96 ) -> element.Element(msg) { 112 97 html.input([ 113 98 attribute.type_("password"), 114 - attribute.name(field.name), 115 - attribute.id(args.id), 116 - attribute.attribute("aria-labelledby", field.label), 99 + attribute.name(config.name), 100 + attribute.id(config.name), 101 + attribute.attribute("aria-labelledby", config.label), 117 102 ]) 118 103 } 119 104 120 105 pub fn password_field() { 121 - Definition( 106 + definition( 122 107 widget: password_widget, 123 108 parse: validation.string, 124 - optional_parse: fn(parse, str) { 125 - case str { 126 - "" -> Ok(option.None) 127 - _ -> parse(str) 128 - } 129 - }, 130 - // We need to have a stub value for each parser. The stubs are used when 131 - // building the decoder and parse functions for the form. 109 + // We need to have a stub value for each definition. The stubs are used when 110 + // building the decoder functions for the form. This is just any value of 111 + // the same type that the parse function returns. 132 112 stub: "", 133 - optional_stub: option.None, 134 113 ) 135 114 } 136 115 ``` ··· 144 123 a function to loop through each field, generating semantic, accessible 145 124 markup for each one. 146 125 147 - The specifics of how you would do this are going 148 - to vary greatly for each project and its styling/markup needs. 126 + The specifics of how you would do this are going to vary greatly for each 127 + project and its styling/markup needs. 149 128 150 129 However, the three `formz_*` libraries mentioned above all provide a 151 130 simple form generator function that you can use as is, or as a starting ··· 215 194 case credentials { 216 195 #("admin" as username, "l33t") -> Ok(username) 217 196 #("admin", _) -> 218 - Error(form |> formz.set_field_error("password", "Wrong password")) 197 + Error(form |> formz.field_error("password", "Wrong password")) 219 198 _ -> 220 - Error(form |> formz.set_field_error("username", "Unknown username")) 199 + Error(form |> formz.field_error("username", "Unknown username")) 221 200 } 222 201 }) 223 202
+41 -62
formz/README.md
··· 11 11 to make that link explicit and easy to manage, while making it really easy 12 12 to make accessible forms. 13 13 14 - Note: This library is not particularly well-suited for generating one-off 15 - forms, but is more intended for use in projects where you have a few forms to 14 + Note: This library is not necessarily well-suited for generating one-off 15 + forms, and is intended for use in projects where you have a few forms to 16 16 manage, and would like to keep the form markup and parsing logic in sync. It 17 17 takes some amount of effort to make an actual form generator with markup and 18 - styles, and that might not be worth it for a one-off form. That said, a simple 18 + styles, and that might not be worth it for a one-off form. That said, a simple 19 19 form generator is provided if you aren't opinionated about your markup. 20 20 21 21 ```sh ··· 29 29 30 30 ```gleam 31 31 import formz 32 - import formz/field.{field} 33 32 import formz_string/definitions 34 33 35 34 pub fn make_form() { 36 - use username <- formz.field( 37 - formz.named("username"), 38 - formz.required(definitions.text_field()), 39 - ) 40 - use password <- formz.field( 41 - formz.named("password"), 42 - formz.required(definitions.password_field()), 43 - ) 35 + use username <- formz.field(formz.named("username"), definitions.text_field()) 36 + use password <- formz.field(formz.named("password"), definitions.password_field()) 44 37 45 38 formz.create_form(#(username, password)) 46 39 } ··· 50 43 51 44 There are two arguments to adding a field to a form (seen above): 52 45 53 - 1. A [Field](https://hexdocs.pm/formz/formz/field.html), which holds specific, 54 - unique details about the field, such as its name, label, help text, disabled 55 - state, etc. 56 - 2. A [Definition](https://hexdocs.pm/formz/formz/definition.html), which 57 - says (A) how to generate the HTML input element for the field, and (B) how 58 - to parse the data from the field. These definitions are reusable and can be 59 - shared across fields, forms and projects. 46 + 1. A `Config`, which holds specific, unique details about the field: its name, 47 + label, help text, and disabled state. 48 + 2. A `Definition`, which says (A) how to generate the HTML input element for 49 + the field, and (B) how to parse the data from the field. These definitions 50 + are reusable and can be shared across fields, forms and projects. 60 51 61 - ### Field details 52 + ### Field config 62 53 63 54 ```gleam 64 - // name is required, the other details are optional 65 - field(named: "username") 66 - |> field.set_label("Username") 67 - |> field.set_help_text("Only alphanumeric characters are allowed.") 55 + // name is required, the other confg are optional 56 + formz.named("username") 57 + |> formz.set_label("Username") 58 + |> formz.set_help_text("Only alphanumeric characters are allowed.") 68 59 ``` 69 60 70 61 ```gleam 71 - field(named: "userid") |> field.make_hidden |> field.set_raw_value("42") 62 + formz.named("userid") |> formz.make_disabled 72 63 ``` 73 64 74 65 ### Field definition 75 66 76 - [Defintions](https://hexdocs.pm/formz/formz/definition.html) are the heavy 77 - compared to the lightness of fields; they take a bit more work to make as they 78 - are intended to be more reusable. 67 + A `Definition` describes how an input works, e.g. how it looks and how it's 68 + parsed. Definitions are intended to be reusable. 69 + 70 + The first role of a `Defintion` is to generate the HTML input for the field. 71 + This library is format-agnostic and you can generate inputs as raw 72 + strings, Lustre elements, Nakai nodes, something else, etc. The second role 73 + of a `Definition` is to parse the raw string data from the input into a 74 + Gleam type. 79 75 80 - The first role of a `Defintion` is to generate the HTML widget for the field. 81 - This library is format-agnostic and you can generate HTML widgets as raw 82 - strings, Lustre elements, Nakai nodes, something else, etc. There are 83 - currently three `formz` libraries that provide common field definitions for the 84 - most common HTML formats. 76 + There are currently three `formz` libraries that provide common field 77 + definitions for the most common HTML inputs: 85 78 86 79 - [formz_string](https://hexdocs.pm/formz_string/) 87 80 - [formz_nakai](https://hexdocs.pm/formz_nakai/) 88 - - [formz_lustre](https://hexdocs.pm/formz_lustre/) (untested in a browser, I've only done this server side) 89 - 90 - The second role of a `Definition` is to parse the data from the field. There 91 - are a two parts to this, as how you parse a field's value depends on if it is 92 - optional or required. Not all scenarios can be cookie-cutter placed into an 93 - `Option`. So you need to provide two parse functions, one for when a field is 94 - required, and a second for when it's optional. 81 + - [formz_lustre](https://hexdocs.pm/formz_lustre/) 95 82 96 83 ```gleam 97 84 /// you won't often need to do this directly (I think??). The idea is that 98 85 /// there'd be libs with the definitions you need. 99 86 100 - import formz/definition.{Definition} 101 - import formz/field 87 + import formz 102 88 import formz/validation 103 - import formz/widget 104 89 import lustre/attribute 105 90 import lustre/element 106 91 import lustre/element/html 107 92 108 93 fn password_widget( 109 - field: field.Field, 110 - args: widget.Args, 94 + config: formz.Config, 95 + state: formz.InputState, 111 96 ) -> element.Element(msg) { 112 97 html.input([ 113 98 attribute.type_("password"), 114 - attribute.name(field.name), 115 - attribute.id(args.id), 116 - attribute.attribute("aria-labelledby", field.label), 99 + attribute.name(config.name), 100 + attribute.id(config.name), 101 + attribute.attribute("aria-labelledby", config.label), 117 102 ]) 118 103 } 119 104 120 105 pub fn password_field() { 121 - Definition( 106 + definition( 122 107 widget: password_widget, 123 108 parse: validation.string, 124 - optional_parse: fn(parse, str) { 125 - case str { 126 - "" -> Ok(option.None) 127 - _ -> parse(str) 128 - } 129 - }, 130 - // We need to have a stub value for each parser. The stubs are used when 131 - // building the decoder and parse functions for the form. 109 + // We need to have a stub value for each definition. The stubs are used when 110 + // building the decoder functions for the form. This is just any value of 111 + // the same type that the parse function returns. 132 112 stub: "", 133 - optional_stub: option.None, 134 113 ) 135 114 } 136 115 ``` ··· 144 123 a function to loop through each field, generating semantic, accessible 145 124 markup for each one. 146 125 147 - The specifics of how you would do this are going 148 - to vary greatly for each project and its styling/markup needs. 126 + The specifics of how you would do this are going to vary greatly for each 127 + project and its styling/markup needs. 149 128 150 129 However, the three `formz_*` libraries mentioned above all provide a 151 130 simple form generator function that you can use as is, or as a starting ··· 215 194 case credentials { 216 195 #("admin" as username, "l33t") -> Ok(username) 217 196 #("admin", _) -> 218 - Error(form |> formz.set_field_error("password", "Wrong password")) 197 + Error(form |> formz.field_error("password", "Wrong password")) 219 198 _ -> 220 - Error(form |> formz.set_field_error("username", "Unknown username")) 199 + Error(form |> formz.field_error("username", "Unknown username")) 221 200 } 222 201 }) 223 202
+1
formz/TODO.md
··· 1 + - better error messages. include field name? 1 2 - form sets 2 3 - csrf token? 3 4 - multiple value fields?
+174 -117
formz/src/formz.gleam
··· 11 11 //// <td>Creating a form</td> 12 12 //// <td> 13 13 //// <a href="#create_form">create_form</a><br> 14 - //// <a href="#require">require</a><br> 15 - //// <a href="#optional">optional</a><br> 14 + //// <a href="#required_field">required_field</a><br> 15 + //// <a href="#field">field</a><br> 16 16 //// <a href="#list">list</a><br> 17 17 //// <a href="#limited_list">limited_list</a><br> 18 18 //// <a href="#subform">subform</a> ··· 51 51 //// <td> 52 52 //// <a href="#get">get</a><br> 53 53 //// <a href="#items">items</a><br> 54 - //// <a href="#set_field_error">set_field_error</a><br> 55 - //// <a href="#set_listfield_errors">set_listfield_errors</a><br> 54 + //// <a href="#field_error">field_error</a><br> 55 + //// <a href="#listfield_errors">listfield_errors</a><br> 56 56 //// <a href="#update">update</a><br> 57 + //// </td> 58 + //// </tr> 59 + //// <tr> 60 + //// <td>Accessing and manipulating config for a form item</td> 61 + //// <td> 57 62 //// <a href="#update_field">update_field</a><br> 58 - //// <a href="#update_listfield">update_listfield</a><br> 59 - //// <a href="#update_subform">update_subform</a> 63 + //// <a href="#set_name">set_name</a><br> 64 + //// <a href="#set_label">set_label</a><br> 65 + //// <a href="#set_help_text">set_help_text</a><br> 66 + //// <a href="#set_disabled">set_disabled</a><br> 67 + //// <a href="#make_disabled">make_disabled</a><br> 60 68 //// </td> 61 69 //// </tr> 62 70 //// </table> ··· 65 73 //// 66 74 //// ```gleam 67 75 //// fn make_form() { 68 - //// use name <- formz.require(field("name"), definitions.text_field()) 76 + //// use name <- formz.required_field(field("name"), definitions.text_field()) 69 77 //// 70 78 //// formz.create_form(name) 71 79 //// } ··· 80 88 //// 81 89 //// ```gleam 82 90 //// fn make_form() { 83 - //// use greeting <- optional(field("greeting"), definitions.text_field()) 84 - //// use name <- optional(field("name"), definitions.text_field()) 91 + //// use greeting <- field(field("greeting"), definitions.text_field()) 92 + //// use name <- field(field("name"), definitions.text_field()) 85 93 //// 86 94 //// formz.create_form(greeting <> " " <> name) 87 95 //// } ··· 94 102 //// } 95 103 //// ``` 96 104 97 - import formz/field 98 - import formz/subform 99 105 import gleam/dict.{type Dict} 100 106 import gleam/int 101 107 import gleam/list 102 108 import gleam/option.{None, Some} 103 109 import gleam/result 104 110 import gleam/string 111 + import justin 105 112 106 113 /// You create this using the `create_form` function. 107 114 /// ··· 119 126 ) 120 127 } 121 128 122 - /// You add an `Item` to a form using the `optional`, `require`, `list`, 129 + /// You add an `Item` to a form using the `field`, `required_field`, `list`, 123 130 /// `limited_list` and `subform` functions. A form is a list of `Item`s and 124 - /// each item is parsed to a single value, which the decode function will 125 - /// choose how to use. 131 + /// each item is parsed to a single value, and then passed to the decode 132 + /// function. 126 133 /// 127 134 /// You primarily only use an `Item` directly when writing a form generator 128 - /// function to output your function to HTML. 135 + /// function to output your form to HTML. 129 136 /// 130 137 /// You can also manipulate an `Item` after a form has been created, to change 131 - /// things like labels, help text, etc. There are specific functions, 132 - /// `update_field`, `update_listfield` and `update_subform`, to help with this, 133 - /// so you don't have to pattern match when updating a specific item. 138 + /// things like labels, help text, etc with `update`. Each item has a `Config` 139 + /// that describes how it works, and this can be updated directly 140 + /// with `update_config`. 134 141 /// 135 142 /// ``` 136 143 /// let form = make_form() 137 - /// form.update_field("name", field.set_label(_, "Full Name")) 144 + /// formz.update_config("name", formz.set_label(_, "Full Name")) 138 145 /// ``` 139 146 pub type Item(widget) { 140 147 /// A single field that (generally speaking) corresponds to a single 141 148 /// HTML input 142 - Field(detail: field.Field, state: InputState, widget: widget) 149 + Field(config: Config, state: InputState, widget: widget) 143 150 /// A single field that a consumer can submit multiple values for. 144 151 ListField( 145 - detail: field.Field, 152 + config: Config, 146 153 states: List(InputState), 147 154 limit_check: LimitCheck, 148 155 widget: widget, 149 156 ) 150 157 /// A group of fields that are added as and then parsed to a single unit. 151 - SubForm(detail: subform.SubForm, items: List(Item(widget))) 158 + SubForm(config: Config, items: List(Item(widget))) 152 159 } 153 160 154 161 /// The state of the an input for a field. This is used to track the current ··· 166 173 Required 167 174 } 168 175 169 - /// A `Definition` describes how a field works, e.g. how it looks and how it's 170 - /// parsed. It is the heavy compared to the lightness of a 171 - /// [Field](https://hexdocs.pm/formz/formz/field.html); 172 - /// definitions take a bit more work to make as they are intended to be reusable. 176 + /// Configuration for the form `Item`. The only required information here is 177 + /// `name` and there is a `named` function to make a config with a name. You 178 + /// can then chain the `set_x` functions to add the other information as needed. 179 + /// 180 + /// ### Example 181 + /// 182 + /// ``` 183 + /// let config = formz.named("name") |> formz.set_label("Full Name") 184 + /// ``` 185 + /// 186 + /// ``` 187 + /// let config = 188 + /// formz.named("name") 189 + /// |> formz.set_label("Full Name") 190 + /// |> formz.set_help_text("Enter your full name") 191 + /// ``` 192 + /// 193 + /// ``` 194 + /// let config = formz.named("name") |> formz.make_disabled 195 + /// ``` 196 + pub type Config { 197 + Config( 198 + /// The name of the field or subform. The only truly required information 199 + /// for a form `Item`. This is used to identify the field in the form. 200 + /// It should be unique for each form, and is untested with any values other 201 + /// than strings solely consisting of alphanumeric characters 202 + /// and underscores. 203 + name: String, 204 + /// This library thinks of a label as required, but will make one for you from 205 + /// the name if you don't provide one via the `field` function. For 206 + /// accessibility reasons, a field should always provide a label and all 207 + /// the maintained form generators will output one. 208 + label: String, 209 + /// Optional help text for the field. This is used to provide additional 210 + /// instructions or context for the field. It is up to the form generator 211 + /// to decide if and how to display this text. 212 + help_text: String, 213 + /// Whether the field is disabled. A disabled field is not editable in 214 + /// the browser. However, there is nothing stopping a user from changing 215 + /// the value or submitting a different value via other means, so (presently) 216 + /// this doesn't mean the value cannot be tampered with. 217 + disabled: Bool, 218 + ) 219 + } 220 + 221 + /// A `Definition` describes how an input works, e.g. how it looks and how it's 222 + /// parsed. Definitions are intended to be reusable. 173 223 /// 174 224 /// The first role of a `Defintion` is to generate the HTML input for the field. 175 225 /// This library is format-agnostic and you can generate inputs as raw ··· 185 235 /// - [formz_lustre](https://hexdocs.pm/formz_lustre/) 186 236 /// 187 237 /// How a definition parses an input value depends on whether a value is required 188 - /// for that input (i.e. whether `optional`, `require`, `list`, or `limited_list` 238 + /// for that input (i.e. whether `field`, `required_field`, `list`, or `limited_list` 189 239 /// was used to add it to the form). If a value is required, the definition is 190 240 /// expected to return a string error if the input is empty, or the `required` type 191 241 /// if it isn't. You can use the `definition` function to create a simple ··· 233 283 /// of how to write one. 234 284 /// 235 285 /// This function takes as its only argument, the number of fields that already 236 - /// have a value. It should return a list of `Unvalidated` `InputState` items 237 - /// that specify if the value is required or not. 286 + /// have a value. It should return either `Ok` with a list of `Unvalidated` 287 + /// `InputState` items if it wants to offer more inputs to consumers of the 288 + /// form, or `Error` amount of inputs that were too many. 238 289 /// 239 290 /// This is used multiple times... when the form is created so we know how many 240 291 /// initial inputs to present, when data is added so we know if we need to add ··· 262 313 /// ``` 263 314 /// ```gleam 264 315 /// fn make_form() { 265 - /// use field1 <- formz.require(field("field1"), definitions.text_field()) 266 - /// use field2 <- formz.require(field("field2"), definitions.text_field()) 267 - /// use field3 <- formz.require(field("field3"), definitions.text_field()) 316 + /// use field1 <- formz.required_field(field("field1"), definitions.text_field()) 317 + /// use field2 <- formz.required_field(field("field2"), definitions.text_field()) 318 + /// use field3 <- formz.required_field(field("field3"), definitions.text_field()) 268 319 /// 269 320 /// formz.create_form(#(field1, field2, field3)) 270 321 /// } ··· 288 339 /// number of times. Don't do anything but create the type with the data you 289 340 /// need! If you need to do decoding that has side effects, you should use 290 341 /// `decode_then_try`. 291 - pub fn optional( 292 - field: field.Field, 342 + pub fn field( 343 + config: Config, 293 344 definition: Definition(widget, _, input_output), 294 345 next: fn(input_output) -> Form(widget, form_output), 295 346 ) -> Form(widget, form_output) { 296 347 add_field( 297 - field, 348 + config, 298 349 Optional, 299 350 definition.widget, 300 351 definition.optional_parse(definition.parse, _), ··· 318 369 /// number of times. Don't do anything but create the type with the data you 319 370 /// need! If you need to do decoding that has side effects, you should use 320 371 /// `decode_then_try`. 321 - pub fn require( 322 - field: field.Field, 372 + pub fn required_field( 373 + config: Config, 323 374 is definition: Definition(widget, required_output, _), 324 375 next next: fn(required_output) -> Form(widget, form_output), 325 376 ) -> Form(widget, form_output) { 326 377 add_field( 327 - field, 378 + config, 328 379 Required, 329 380 definition.widget, 330 381 definition.parse, ··· 334 385 } 335 386 336 387 fn add_field( 337 - field: field.Field, 388 + config: Config, 338 389 requirement: Requirement, 339 390 widget: widget, 340 391 parse_field: fn(String) -> Result(input_output, String), ··· 348 399 349 400 // prepend the new field to the items from the form we got in the previous step. 350 401 let updated_items = [ 351 - Field(field, Unvalidated("", requirement), widget), 402 + Field(config, Unvalidated("", requirement), widget), 352 403 ..next_form.items 353 404 ] 354 405 ··· 483 534 /// } 484 535 pub fn limited_list( 485 536 limit_check: fn(Int) -> Result(List(InputState), Int), 486 - field: field.Field, 537 + config: Config, 487 538 is definition: Definition(widget, required_output, _), 488 539 next next: fn(List(required_output)) -> Form(widget, form_output), 489 540 ) -> Form(widget, form_output) { ··· 495 546 let initial_fields = limit_check(0) |> result.unwrap([]) 496 547 // prepend the new field to the items from the form we got in the previous step. 497 548 let updated_items = [ 498 - ListField(field, initial_fields, limit_check, definition.widget), 549 + ListField(config, initial_fields, limit_check, definition.widget), 499 550 ..next_form.items 500 551 ] 501 552 ··· 647 698 /// need! If you need to do decoding that has side effects, you should use 648 699 /// `decode_then_try`. 649 700 pub fn list( 650 - field: field.Field, 701 + config: Config, 651 702 is definition: Definition(widget, required_output, _), 652 703 next next: fn(List(required_output)) -> Form(widget, form_output), 653 704 ) -> Form(widget, form_output) { 654 - limited_list(limit_at_most(1_000_000), field, definition, next) 705 + limited_list(limit_at_most(1_000_000), config, definition, next) 655 706 } 656 707 657 708 fn add_prefix_to_item(item: Item(widget), prefix: String) -> Item(widget) { 658 709 case item { 659 710 Field(item_details, state, widget) -> { 660 - let name = prefix <> "." <> item_details.name 661 - Field(item_details |> field.set_name(name), state, widget) 711 + let new_name = prefix <> "." <> item_details.name 712 + Field(item_details |> set_name(new_name), state, widget) 662 713 } 663 714 ListField(item_details, states, limit_check, widget) -> { 664 - let name = prefix <> "." <> item_details.name 665 - ListField( 666 - item_details |> field.set_name(name), 667 - states, 668 - limit_check, 669 - widget, 670 - ) 715 + let new_name = prefix <> "." <> item_details.name 716 + ListField(item_details |> set_name(new_name), states, limit_check, widget) 671 717 } 672 718 SubForm(item_details, sub_items) -> { 673 - let name = prefix <> "." <> item_details.name 674 - SubForm(item_details |> subform.set_name(name), sub_items) 719 + let new_name = prefix <> "." <> item_details.name 720 + SubForm(item_details |> set_name(new_name), sub_items) 675 721 } 676 722 } 677 723 } ··· 689 735 /// need! If you need to do decoding that has side effects, you should use 690 736 /// `decode_then_try`. 691 737 pub fn subform( 692 - subform: subform.SubForm, 738 + config: Config, 693 739 form: Form(widget, sub_output), 694 740 next: fn(sub_output) -> Form(widget, form_output), 695 741 ) -> Form(widget, form_output) { 696 742 let next_form = next(form.stub) 697 743 698 - let sub_items = form.items |> list.map(add_prefix_to_item(_, subform.name)) 699 - let subform = SubForm(subform, sub_items) 744 + let sub_items = form.items |> list.map(add_prefix_to_item(_, config.name)) 745 + let subform = SubForm(config, sub_items) 700 746 let updated_items = [subform, ..next_form.items] 701 747 702 748 let decode = fn(items: List(Item(widget))) { ··· 763 809 data: Dict(String, List(String)), 764 810 ) -> List(Item(widget)) { 765 811 list.map(items, fn(item) { 766 - let values = dict.get(data, get_item_name(item)) 812 + let values = dict.get(data, item.config.name) 767 813 case item, values { 768 814 Field(detail, state, widget), Ok([_, ..] as values) -> { 769 815 let assert Ok(last) = list.last(values) ··· 842 888 /// |> decode_then_try(fn(username, form) { 843 889 /// case is_username_taken(username) { 844 890 /// Ok(false) -> Ok(form) 845 - /// Ok(true) -> set_field_error(form, "username", "Username is taken") 891 + /// Ok(true) -> field_error(form, "username", "Username is taken") 846 892 /// } 847 893 /// } 848 894 pub fn decode_then_try( ··· 987 1033 /// let form = make_form() 988 1034 /// update_field(form, "name", field.set_label(_, "Full Name")) 989 1035 /// ``` 990 - pub fn update_field( 1036 + pub fn update_config( 991 1037 form: Form(widget, output), 992 1038 name: String, 993 - fun: fn(field.Field) -> field.Field, 1039 + fun: fn(Config) -> Config, 994 1040 ) -> Form(widget, output) { 995 1041 update(form, name, fn(item) { 996 1042 case item { 997 - Field(field, ..) -> Field(..item, detail: fun(field)) 998 - _ -> item 999 - } 1000 - }) 1001 - } 1002 - 1003 - /// Update the `ListField` [details]](https://hexdocs.pm/formz/formz/field.html) with 1004 - /// the given name using the provided function. If multiple items have the same 1005 - /// name, it will be called on all of them. If no items have the given name, 1006 - /// or an item with the given name exists but isn't a `ListField`, this function 1007 - /// will do nothing. 1008 - /// 1009 - /// ```gleam 1010 - /// let form = make_form() 1011 - /// update(form, "name", field.set_label(_, "Full Name")) 1012 - /// ``` 1013 - pub fn update_listfield( 1014 - form: Form(widget, output), 1015 - name: String, 1016 - fun: fn(field.Field) -> field.Field, 1017 - ) -> Form(widget, output) { 1018 - update(form, name, fn(item) { 1019 - case item { 1020 - ListField(field, ..) -> ListField(..item, detail: fun(field)) 1021 - _ -> item 1022 - } 1023 - }) 1024 - } 1025 - 1026 - /// Update the [`SubForm`](https://hexdocs.pm/formz/formz/subform.html) with 1027 - /// the given name using the provided function. If multiple subforms have the same 1028 - /// name, it will be called on all of them. If no items have the given name, 1029 - /// or an item with the given name exists but isn't a `SubForm`, this function 1030 - /// will do nothing. 1031 - /// 1032 - /// ```gleam 1033 - /// let form = make_form() 1034 - /// update(form, "name", subform.set_help_text(_, "...")) 1035 - /// ``` 1036 - pub fn update_subform( 1037 - form: Form(widget, output), 1038 - name: String, 1039 - fun: fn(subform.SubForm) -> subform.SubForm, 1040 - ) -> Form(widget, output) { 1041 - update(form, name, fn(item) { 1042 - case item { 1043 - SubForm(subform, items) -> SubForm(fun(subform), items) 1044 - _ -> item 1043 + Field(details, ..) -> Field(..item, config: fun(details)) 1044 + ListField(details, ..) -> ListField(..item, config: fun(details)) 1045 + SubForm(details, ..) -> SubForm(..item, config: fun(details)) 1045 1046 } 1046 1047 }) 1047 1048 } ··· 1052 1053 /// ### Example 1053 1054 /// 1054 1055 /// ``` 1055 - /// set_field_error(form, "username", "Username is taken") 1056 + /// field_error(form, "username", "Username is taken") 1056 1057 /// ``` 1057 - pub fn set_field_error( 1058 + pub fn field_error( 1058 1059 form: Form(widget, output), 1059 1060 name: String, 1060 1061 str: String, ··· 1072 1073 /// takes a list of Results, where the Ok means the input is `Valid` and 1073 1074 /// `Error` means the input is `Invalid` with the given error message. 1074 1075 /// 1076 + /// This does not clear any existing errors, it will just set the errors marked 1077 + /// in the input list. If you want to clear errors you'll have to use the 1078 + /// `update` function and do it manually. 1079 + /// 1075 1080 /// ### Example 1076 1081 /// 1077 1082 /// ``` 1078 - /// set_listfield_errors(form, "pet_names", [Ok(Nil), Ok(Nil), Error("Must be a cat")]) 1083 + /// listfield_errors(form, "pet_names", [Ok(Nil), Ok(Nil), Error("Must be a cat")]) 1079 1084 /// ``` 1080 - pub fn set_listfield_errors( 1085 + pub fn listfield_errors( 1081 1086 form: Form(widget, output), 1082 1087 name: String, 1083 1088 errors: List(Result(Nil, String)), ··· 1130 1135 } 1131 1136 1132 1137 /// Create a `Definition` that can parse to any type if the field is optional. 1133 - /// This takes two functions. The first, `parse`, is the "required"" parse 1138 + /// This takes two functions. The first, `parse`, is the "required" parse 1134 1139 /// function, which takes the raw string value, and turns it into the required 1135 1140 /// type. The second, `optional_parse`, is a function that takes the normal 1136 1141 /// parse function and the raw string value, and it is supposed to check the 1137 - /// input string: if it is empty, return an `Ok` with the `optional_stub` 1138 - /// value; and if it's not empty use the normal parse function. 1142 + /// input string: if it is empty, return an `Ok` with a value of the optional 1143 + /// type; and if it's not empty use the normal parse function. 1139 1144 /// 1140 1145 /// See [formz_string](https://hexdocs.pm/formz_string/formz_string/definitions.html) 1141 1146 /// for more examples of making widgets and definitions. ··· 1249 1254 } 1250 1255 }) 1251 1256 } 1257 + 1258 + /// Create a field with the given name. 1259 + /// 1260 + /// It uses 1261 + /// [justin.sentence_case](https://hexdocs.pm/justin/justin.html#sentence_case) 1262 + /// to create an initial label. You can override the label with the `set_label` 1263 + /// function. I don't know if this is very english-centric, so let me know if 1264 + /// this is a bad experience in other languages and I'll consider 1265 + /// something else. 1266 + /// 1267 + /// ```gleam 1268 + /// field("name") 1269 + /// |> set_label("Full Name") 1270 + /// ``` 1271 + pub fn named(name: String) -> Config { 1272 + Config( 1273 + name: name, 1274 + label: justin.sentence_case(name), 1275 + help_text: "", 1276 + disabled: False, 1277 + ) 1278 + } 1279 + 1280 + /// Set the name of the field. This is the key that will be used for the data. 1281 + pub fn set_name(config: Config, name: String) -> Config { 1282 + Config(..config, name:) 1283 + } 1284 + 1285 + /// Set the label of the field. This is the primary text that will be displayed 1286 + /// to the user about the field. 1287 + pub fn set_label(config: Config, label: String) -> Config { 1288 + Config(..config, label:) 1289 + } 1290 + 1291 + /// Additional instructions or help text to display to the user about the field. 1292 + pub fn set_help_text(config: Config, help_text: String) -> Config { 1293 + Config(..config, help_text:) 1294 + } 1295 + 1296 + /// Set the `disabled` flag directly. 1297 + pub fn set_disabled(config: Config, disabled: Bool) -> Config { 1298 + Config(..config, disabled:) 1299 + } 1300 + 1301 + /// Mark the form `Item` as disabled. This will prevent the user from 1302 + /// interacting with the field. If you do this for a subform, it will 1303 + /// only work if the form generator renders the subform as a `fieldset`. 1304 + /// For example, HTML does not allow you to mark inputs in a `<div>` 1305 + /// disabled as group but you can do this with a `<fieldset>`. 1306 + pub fn make_disabled(config: Config) -> Config { 1307 + set_disabled(config, True) 1308 + }
-77
formz/src/formz/field.gleam
··· 1 - //// A `Field` is the first argument needed to add a field to a form. It contains 2 - //// information about this specific field, like it's name, label, or (optional) 3 - //// help_text. There is a convenience function to create a field with just a name, 4 - //// and then you can use the rest of the functions to set just the values you 5 - //// need to change. 6 - //// 7 - //// ```gleam 8 - //// field("name") |> set_label("Full Name") 9 - //// ``` 10 - //// 11 - //// ```gleam 12 - //// field("name") 13 - //// |> set_label("Full Name") 14 - //// |> set_help_text("You can make one up if you'd like.") 15 - //// ``` 16 - 17 - import justin 18 - 19 - pub type Field { 20 - Field( 21 - /// The name of the field. The only truly required information for a field. 22 - /// This is used to identify the field in the form. It should be unique for 23 - /// each form, and is untested with any values other than strings solely 24 - /// consisting of alphanumeric characters and underscores. 25 - name: String, 26 - /// This library thinks of a label as required, but will make one for you from 27 - /// the name if you don't provide one via the `field` function. For 28 - /// accessibility reasons, a field should always provide a label and all 29 - /// the maintained form generators will output one. 30 - label: String, 31 - /// Optional help text for the field. This is used to provide additional 32 - /// instructions or context for the field. It is up to the form generator 33 - /// to decide if and how to display this text. 34 - help_text: String, 35 - /// Whether the field is disabled. A disabled field is not editable in 36 - /// the browser. However, there is nothing stopping a user from changing 37 - /// the value or submitting a different value via other means, so (presently) 38 - /// this doesn't mean the value cannot be tampered with. 39 - disabled: Bool, 40 - ) 41 - } 42 - 43 - /// Create a field with the given name. It uses [justin.sentence_case](https://hexdocs.pm/justin/justin.html#sentence_case) 44 - /// to create a label. You can override the label with the `set_label` function. 45 - /// 46 - /// ```gleam 47 - /// field("name") 48 - /// |> set_label("Full Name") 49 - /// ``` 50 - pub fn field(named name: String) -> Field { 51 - Field( 52 - name: name, 53 - label: justin.sentence_case(name), 54 - help_text: "", 55 - disabled: False, 56 - ) 57 - } 58 - 59 - pub fn set_name(field: Field, name: String) -> Field { 60 - Field(..field, name:) 61 - } 62 - 63 - pub fn set_label(field: Field, label: String) -> Field { 64 - Field(..field, label:) 65 - } 66 - 67 - pub fn set_help_text(field: Field, help_text: String) -> Field { 68 - Field(..field, help_text:) 69 - } 70 - 71 - pub fn set_disabled(field: Field, disabled: Bool) -> Field { 72 - Field(..field, disabled:) 73 - } 74 - 75 - pub fn make_disabled(field: Field) -> Field { 76 - set_disabled(field, True) 77 - }
-46
formz/src/formz/subform.gleam
··· 1 - //// Details about a subform being added to a form. There is a convenience 2 - //// function to create a field with just a name, and then you can use the rest 3 - //// of the functions to set just the values you need to change. 4 - 5 - import justin 6 - 7 - pub type SubForm { 8 - SubForm( 9 - /// The name of the subform. This is used to prefix all the fields of the 10 - /// subform, so it should be unique for each subform added to aform. 11 - /// It is untested with any values other than strings consisting solely 12 - /// of alphanumeric characters and underscores. 13 - name: String, 14 - /// The label of the subform. This is completely optional, but if the 15 - /// subform is rendered inside a `<fieldset>` then it is [recommended](https://www.w3.org/WAI/tutorials/forms/grouping/) 16 - /// to have a `<legend>` with this label. 17 - label: String, 18 - /// Help text for the subform. There is less of a standard for this, but 19 - /// again, if rendered in a `<fieldset>` then `area-describedby` can be used 20 - /// to point to an element with this help text. 21 - help_text: String, 22 - ) 23 - } 24 - 25 - /// Create a subform with the given name. It uses [justin.sentence_case](https://hexdocs.pm/justin/justin.html#sentence_case) 26 - /// to create a label. You can override the label with the `set_label` function. 27 - /// 28 - /// ```gleam 29 - /// subform("address") 30 - /// |> set_label("Shipping Address") 31 - /// ``` 32 - pub fn subform(name) { 33 - SubForm(name, justin.sentence_case(name), "") 34 - } 35 - 36 - pub fn set_name(sub: SubForm, name: String) -> SubForm { 37 - SubForm(..sub, name:) 38 - } 39 - 40 - pub fn set_label(sub: SubForm, label: String) -> SubForm { 41 - SubForm(..sub, label:) 42 - } 43 - 44 - pub fn set_help_text(sub: SubForm, help_text: String) -> SubForm { 45 - SubForm(..sub, help_text:) 46 - }
+32 -34
formz/test/formz_test.gleam
··· 1 1 import formz.{Invalid, Optional, Required, Unvalidated, Valid} 2 - import formz/field.{field} 3 - import formz/subform.{subform} 4 2 import formz/validation 5 3 import gleam/string 6 4 import gleeunit ··· 68 66 } 69 67 70 68 fn one_field_form() { 71 - use a <- formz.optional(field("a"), text_field()) 69 + use a <- formz.field(formz.named("a"), text_field()) 72 70 formz.create_form("hello " <> a) 73 71 } 74 72 75 73 fn two_field_form() { 76 - use a <- formz.optional(field("a"), text_field()) 77 - use b <- formz.optional(field("b"), text_field()) 74 + use a <- formz.field(formz.named("a"), text_field()) 75 + use b <- formz.field(formz.named("b"), text_field()) 78 76 79 77 formz.create_form(#(a, b)) 80 78 } 81 79 82 80 fn three_field_form() { 83 - use a <- formz.optional( 84 - field("x") |> field.set_name("a") |> field.set_label("A"), 81 + use a <- formz.field( 82 + formz.named("x") |> formz.set_name("a") |> formz.set_label("A"), 85 83 text_field() 86 84 |> formz.verify(fn(str) { 87 85 case string.length(str) > 3 { ··· 91 89 }), 92 90 ) 93 91 94 - use b <- formz.optional( 95 - field(named: "b"), 92 + use b <- formz.field( 93 + formz.named("b"), 96 94 integer_field() 97 95 |> formz.verify(fn(i) { 98 96 case i > 0 { ··· 102 100 }), 103 101 ) 104 102 105 - use c <- formz.optional( 106 - field(named: "c") |> field.set_name("c") |> field.set_label("C"), 103 + use c <- formz.field( 104 + formz.named("c") |> formz.set_name("c") |> formz.set_label("C"), 107 105 float_field(), 108 106 ) 109 107 ··· 167 165 pub fn parse_single_field_form_with_error_test() { 168 166 let assert Error(f) = 169 167 { 170 - use a <- formz.optional(field("a"), boolean_field()) 168 + use a <- formz.field(formz.named("a"), boolean_field()) 171 169 formz.create_form(a) 172 170 } 173 171 |> formz.data([#("a", "world")]) ··· 236 234 237 235 pub fn sub_form_test() { 238 236 let f1 = { 239 - use a <- formz.require(field("a"), integer_field()) 240 - use b <- formz.require(field("b"), integer_field()) 241 - use c <- formz.require(field("c"), integer_field()) 237 + use a <- formz.required_field(formz.named("a"), integer_field()) 238 + use b <- formz.required_field(formz.named("b"), integer_field()) 239 + use c <- formz.required_field(formz.named("c"), integer_field()) 242 240 243 241 formz.create_form(#(a, b, c)) 244 242 } 245 243 246 244 let f2 = { 247 - use a <- formz.subform(subform("name"), f1) 248 - use b <- formz.require(field("d"), integer_field()) 245 + use a <- formz.subform(formz.named("name"), f1) 246 + use b <- formz.required_field(formz.named("d"), integer_field()) 249 247 250 248 formz.create_form(#(a, b)) 251 249 } ··· 388 386 389 387 pub fn sub_form_error_test() { 390 388 let f1 = { 391 - use a <- formz.optional(field("a"), integer_field()) 392 - use b <- formz.require(field("b"), integer_field()) 393 - use c <- formz.optional(field("c"), integer_field()) 389 + use a <- formz.field(formz.named("a"), integer_field()) 390 + use b <- formz.required_field(formz.named("b"), integer_field()) 391 + use c <- formz.field(formz.named("c"), integer_field()) 394 392 395 393 formz.create_form(#(a, b, c)) 396 394 } 397 395 398 396 let f2 = { 399 - use a <- formz.subform(subform("name"), f1) 400 - use b <- formz.optional(field("d"), integer_field()) 397 + use a <- formz.subform(formz.named("name"), f1) 398 + use b <- formz.field(formz.named("d"), integer_field()) 401 399 402 400 formz.create_form(#(a, b)) 403 401 } ··· 463 461 // can change field 464 462 let assert Error(form) = 465 463 formz.decode_then_try(f, fn(form, _) { 466 - Error(form |> formz.set_field_error("a", "woops")) 464 + Error(form |> formz.field_error("a", "woops")) 467 465 }) 468 466 formz.get_states(form) 469 467 |> should.equal([ ··· 473 471 ]) 474 472 475 473 let f = { 476 - use a <- formz.list(field("a"), float_field()) 474 + use a <- formz.list(formz.named("a"), float_field()) 477 475 formz.create_form(a) 478 476 } 479 477 480 478 formz.data(f, [#("a", "1"), #("a", "2")]) 481 479 |> formz.decode_then_try(fn(form, _) { 482 480 Error( 483 - formz.set_listfield_errors(form, "a", [Error("woops 1"), Error("woops 2")]), 481 + formz.listfield_errors(form, "a", [Error("woops 1"), Error("woops 2")]), 484 482 ) 485 483 }) 486 484 |> get_form_from_error_result ··· 492 490 493 491 formz.data(f, [#("a", "1"), #("a", "2")]) 494 492 |> formz.decode_then_try(fn(form, _) { 495 - Error(formz.set_listfield_errors(form, "a", [Error("woops"), Ok(Nil)])) 493 + Error(formz.listfield_errors(form, "a", [Error("woops"), Ok(Nil)])) 496 494 }) 497 495 |> get_form_from_error_result 498 496 |> formz.get_states ··· 500 498 501 499 formz.data(f, [#("a", "1"), #("a", "2")]) 502 500 |> formz.decode_then_try(fn(form, _) { 503 - Error(formz.set_listfield_errors(form, "a", [Ok(Nil), Error("woops")])) 501 + Error(formz.listfield_errors(form, "a", [Ok(Nil), Error("woops")])) 504 502 }) 505 503 |> get_form_from_error_result 506 504 |> formz.get_states ··· 509 507 510 508 pub fn list_test() { 511 509 let f = { 512 - use a <- formz.list(field("a"), float_field()) 513 - use b <- formz.list(field("b"), float_field()) 514 - use c <- formz.list(field("c"), float_field()) 510 + use a <- formz.list(formz.named("a"), float_field()) 511 + use b <- formz.list(formz.named("b"), float_field()) 512 + use c <- formz.list(formz.named("c"), float_field()) 515 513 516 514 formz.create_form(#(a, b, c)) 517 515 } ··· 602 600 let zero_extra = { 603 601 use a <- formz.limited_list( 604 602 formz.simple_limit_check(1, 4, 0), 605 - field("a"), 603 + formz.named("a"), 606 604 integer_field(), 607 605 ) 608 606 ··· 611 609 let one_extra = { 612 610 use a <- formz.limited_list( 613 611 formz.simple_limit_check(1, 4, 1), 614 - field("a"), 612 + formz.named("a"), 615 613 integer_field(), 616 614 ) 617 615 ··· 621 619 let two_extra = { 622 620 use a <- formz.limited_list( 623 621 formz.simple_limit_check(1, 4, 2), 624 - field("a"), 622 + formz.named("a"), 625 623 integer_field(), 626 624 ) 627 625 ··· 695 693 let f = { 696 694 use a <- formz.limited_list( 697 695 formz.limit_between(2, 3), 698 - field("a"), 696 + formz.named("a"), 699 697 integer_field(), 700 698 ) 701 699
-10
formz_demo/src/formz_demo/example/defaults.gleam
··· 1 1 import formz 2 - import formz_lustre/simple as lustre_simple 3 - import formz_nakai/simple as nakai_simple 4 2 import formz_string/simple 5 3 import lustre/element 6 4 import nakai ··· 24 22 25 23 pub fn format_string_form(form) -> String { 26 24 simple.generate(form) 27 - } 28 - 29 - pub fn format_lustre_form(form) -> element.Element(msg) { 30 - lustre_simple.generate(form) 31 - } 32 - 33 - pub fn format_nakai_form(form) -> html.Node { 34 - nakai_simple.generate(form) 35 25 } 36 26 37 27 pub fn formatted_string_form_to_string(str) -> String {
+12 -14
formz_demo/src/formz_demo/examples/all_the_inputs.gleam
··· 1 1 import formz 2 - import formz/field.{field} 3 2 import formz_string/definition 4 3 import formz_string/widget 5 4 6 5 pub fn make_form() { 7 - use a <- formz.optional(field("text"), definition.text_field()) 8 - use b <- formz.optional(field("int"), definition.integer_field()) 9 - use c <- formz.optional(field("number"), definition.number_field()) 10 - use d <- formz.optional(field("bool"), definition.boolean_field()) 11 - use e <- formz.optional(field("email"), definition.email_field()) 12 - use f <- formz.optional(field("password"), definition.password_field()) 13 - use g <- formz.optional( 14 - field("choices"), 6 + use a <- formz.field(formz.named("text"), definition.text_field()) 7 + use b <- formz.field(formz.named("int"), definition.integer_field()) 8 + use c <- formz.field(formz.named("number"), definition.number_field()) 9 + use d <- formz.field(formz.named("bool"), definition.boolean_field()) 10 + use e <- formz.field(formz.named("email"), definition.email_field()) 11 + use f <- formz.field(formz.named("password"), definition.password_field()) 12 + use g <- formz.field( 13 + formz.named("choices"), 15 14 definition.choices_field(letters(), A), 16 15 ) 17 - use h <- formz.optional( 18 - field("list"), 16 + use h <- formz.field( 17 + formz.named("list"), 19 18 definition.list_field(["Dog", "Cat", "Ant"]), 20 19 ) 21 - use i <- formz.optional( 22 - field("textarea_widget"), 20 + use i <- formz.field( 21 + formz.named("textarea_widget"), 23 22 definition.text_field() 24 23 |> formz.widget(widget.textarea_widget()), 25 24 ) 26 - 27 25 formz.create_form(#(a, b, c, d, e, f, g, h, i)) 28 26 } 29 27
+8 -3
formz_demo/src/formz_demo/examples/custom_output.gleam
··· 1 1 import formz.{Field} 2 - import formz/field.{field} 3 2 import formz_lustre/definition 4 3 import formz_lustre/widget 5 4 import lustre/attribute 6 5 import lustre/element/html 7 6 8 7 pub fn make_form() { 9 - use username <- formz.require(field("username"), definition.text_field()) 10 - use password <- formz.require(field("password"), definition.password_field()) 8 + use username <- formz.required_field( 9 + formz.named("username"), 10 + definition.text_field(), 11 + ) 12 + use password <- formz.required_field( 13 + formz.named("password"), 14 + definition.password_field(), 15 + ) 11 16 12 17 formz.create_form(#(username, password)) 13 18 }
+1 -2
formz_demo/src/formz_demo/examples/hello_world.gleam
··· 1 1 import formz 2 - import formz/field.{field} 3 2 import formz_string/definition 4 3 5 4 pub fn make_form() { 6 - use name <- formz.require(field("name"), definition.text_field()) 5 + use name <- formz.required_field(formz.named("name"), definition.text_field()) 7 6 formz.create_form("Hello " <> name) 8 7 }
+6 -7
formz_demo/src/formz_demo/examples/hidden_inputs.gleam
··· 1 1 import formz 2 - import formz/field.{field} 3 2 import formz_string/definition 4 3 import formz_string/widget 5 4 6 5 pub fn make_form() { 7 - use id1 <- formz.require( 8 - field("id_1"), 6 + use id1 <- formz.required_field( 7 + formz.named("id_1"), 9 8 definition.make_hidden(definition.integer_field()), 10 9 ) 11 - use id2 <- formz.require( 12 - field("id_2"), 10 + use id2 <- formz.required_field( 11 + formz.named("id_2"), 13 12 definition.integer_field() |> definition.make_hidden, 14 13 ) 15 - use id3 <- formz.require( 16 - field("id_3"), 14 + use id3 <- formz.required_field( 15 + formz.named("id_3"), 17 16 definition.integer_field() |> formz.widget(widget.hidden_widget()), 18 17 ) 19 18 formz.create_form(#(id1, id2, id3))
+11 -10
formz_demo/src/formz_demo/examples/labels.gleam
··· 1 1 import formz 2 - import formz/field.{field} 3 2 import formz_string/definition 4 3 5 4 pub fn make_form() { 6 - use name <- formz.require(field(named: "name"), is: definition.text_field()) 7 - use age <- formz.require( 8 - field("age") |> field.set_label("Age"), 9 - is: definition.integer_field(), 5 + use name <- formz.required_field(formz.named("name"), definition.text_field()) 6 + use age <- formz.required_field( 7 + formz.named("age") 8 + |> formz.set_label("Age") 9 + |> formz.set_help_text("Please enter your age"), 10 + definition.integer_field(), 10 11 ) 11 - use height <- formz.require( 12 - field("height") 13 - |> field.set_label("Height (cm)") 14 - |> field.set_help_text("Please enter your height in centimeters"), 15 - is: definition.integer_field(), 12 + use height <- formz.required_field( 13 + formz.named("height") 14 + |> formz.set_label("Height (cm)") 15 + |> formz.set_help_text("Please enter your height in centimeters"), 16 + definition.integer_field(), 16 17 ) 17 18 18 19 formz.create_form(#(name, age, height))
+5 -5
formz_demo/src/formz_demo/examples/list_fields.gleam
··· 1 1 import formz 2 - import formz/field.{field} 2 + 3 3 import formz_string/definition 4 4 import gleam/string 5 5 6 6 pub fn make_form() { 7 7 use cats <- formz.list( 8 - field("cats") |> field.set_help_text("Any number of cats"), 8 + formz.named("cats") |> formz.set_help_text("Any number of cats"), 9 9 definition.text_field() 10 10 |> formz.verify(fn(value) { 11 11 case string.length(value) > 3 { ··· 16 16 ) 17 17 use dogs <- formz.limited_list( 18 18 formz.limit_at_least(1), 19 - field("dogs") |> field.set_help_text("At least 1 dog"), 19 + formz.named("dogs") |> formz.set_help_text("At least 1 dog"), 20 20 definition.text_field(), 21 21 ) 22 22 use fish <- formz.limited_list( 23 23 formz.limit_at_most(2), 24 - field("fish") |> field.set_help_text("At most 2 fish"), 24 + formz.named("fish") |> formz.set_help_text("At most 2 fish"), 25 25 definition.text_field(), 26 26 ) 27 27 use hamsters <- formz.limited_list( 28 28 formz.limit_between(2, 4), 29 - field("hamsters") |> field.set_help_text("Between 2 and 4 hamsters"), 29 + formz.named("hamsters") |> formz.set_help_text("Between 2 and 4 hamsters"), 30 30 definition.text_field(), 31 31 ) 32 32
+11 -6
formz_demo/src/formz_demo/examples/login.gleam
··· 1 - import formz 2 - import formz/field.{field} 1 + import formz.{named} 3 2 import formz_string/definition 4 3 import wisp 5 4 ··· 12 11 } 13 12 14 13 pub fn make_form() { 15 - use username <- formz.require(field("username"), definition.text_field()) 16 - use password <- formz.require(field("password"), definition.password_field()) 14 + use username <- formz.required_field( 15 + named("username"), 16 + definition.text_field(), 17 + ) 18 + use password <- formz.required_field( 19 + named("password"), 20 + definition.password_field(), 21 + ) 17 22 18 23 formz.create_form(Credentials(username, password)) 19 24 } ··· 26 31 Credentials("admin", "l33t") -> Ok(User(credentials.username)) 27 32 Credentials("admin", _) -> 28 33 form 29 - |> formz.set_field_error("password", "Wrong password") 34 + |> formz.field_error("password", "Wrong password") 30 35 |> Error 31 36 Credentials(_, _) -> 32 37 form 33 - |> formz.set_field_error("username", "Wrong username") 38 + |> formz.field_error("username", "Wrong username") 34 39 |> Error 35 40 } 36 41 })
+19 -14
formz_demo/src/formz_demo/examples/require_all_the_inputs.gleam
··· 1 - import formz 2 - import formz/field.{field} 1 + import formz.{named} 3 2 import formz_string/definition 4 3 import formz_string/widget 5 4 6 5 pub fn make_form() { 7 - use a <- formz.require(field("text"), definition.text_field()) 8 - use b <- formz.require(field("int"), definition.integer_field()) 9 - use c <- formz.require(field("number"), definition.number_field()) 10 - use d <- formz.require(field("bool"), definition.boolean_field()) 11 - use e <- formz.require(field("email"), definition.email_field()) 12 - use f <- formz.require(field("password"), definition.password_field()) 13 - use g <- formz.require( 14 - field("choices"), 6 + use a <- formz.required_field(named("text"), definition.text_field()) 7 + use b <- formz.required_field(formz.named("int"), definition.integer_field()) 8 + use c <- formz.required_field( 9 + formz.named("number"), 10 + definition.number_field(), 11 + ) 12 + use d <- formz.required_field(formz.named("bool"), definition.boolean_field()) 13 + use e <- formz.required_field(formz.named("email"), definition.email_field()) 14 + use f <- formz.required_field( 15 + formz.named("password"), 16 + definition.password_field(), 17 + ) 18 + use g <- formz.required_field( 19 + formz.named("choices"), 15 20 definition.choices_field(letters(), stub: A), 16 21 ) 17 - use h <- formz.require( 18 - field("list"), 22 + use h <- formz.required_field( 23 + formz.named("list"), 19 24 definition.list_field(["Dog", "Cat", "Ant"]), 20 25 ) 21 - use i <- formz.require( 22 - field("textarea_widget"), 26 + use i <- formz.required_field( 27 + formz.named("textarea_widget"), 23 28 definition.text_field() 24 29 |> formz.widget(widget.textarea_widget()), 25 30 )
+11 -10
formz_demo/src/formz_demo/examples/sub_form.gleam
··· 1 1 import formz 2 - import formz/field.{field} 3 - import formz/subform.{subform} 4 2 import formz_string/definition 5 3 6 4 pub fn make_form() { 7 - use billing_address <- formz.subform(subform("billing"), address_form()) 8 - use shipping_address <- formz.subform(subform("shipping"), address_form()) 5 + use billing_address <- formz.subform(formz.named("billing"), address_form()) 6 + use shipping_address <- formz.subform(formz.named("shipping"), address_form()) 9 7 10 8 formz.create_form(#(billing_address, shipping_address)) 11 9 } 12 10 13 11 fn address_form() { 14 - use street <- formz.require(field("street"), definition.text_field()) 15 - use city <- formz.require(field("city"), definition.text_field()) 16 - use state <- formz.require( 17 - field("state"), 12 + use street <- formz.required_field( 13 + formz.named("street"), 14 + definition.text_field(), 15 + ) 16 + use city <- formz.required_field(formz.named("city"), definition.text_field()) 17 + use state <- formz.required_field( 18 + formz.named("state"), 18 19 definition.list_field(states_list()), 19 20 ) 20 - use postal_code <- formz.require( 21 - field.field("postal_code"), 21 + use postal_code <- formz.required_field( 22 + formz.named("postal_code"), 22 23 definition.text_field(), 23 24 ) 24 25
+1 -1
formz_lustre/gleam.toml
··· 10 10 formz = { path = "../formz" } 11 11 # formz = ">= 1.0.0 and < 2.0.0" 12 12 gleam_stdlib = ">= 0.34.0 and < 2.0.0" 13 - lustre = ">= 4.5.1 and < 5.0.0" 13 + lustre = ">= 4.6.3 and < 5.0.0" 14 14 15 15 [dev-dependencies] 16 16 gleeunit = ">= 1.0.0 and < 2.0.0"
+3 -3
formz_lustre/manifest.toml
··· 6 6 { name = "formz_string", version = "1.0.0", build_tools = ["gleam"], requirements = ["formz", "gleam_stdlib"], source = "local", path = "../formz_string" }, 7 7 { name = "gleam_erlang", version = "0.33.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "A1D26B80F01901B59AABEE3475DD4C18D27D58FA5C897D922FCB9B099749C064" }, 8 8 { name = "gleam_json", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "0A57FB5666E695FD2BEE74C0428A98B0FC11A395D2C7B4CDF5E22C5DD32C74C6" }, 9 - { name = "gleam_otp", version = "0.15.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "E9ED3DF7E7285DA0C440F46AE8236ADC8475E8CCBEE4899BF09A8468DA3F9187" }, 10 - { name = "gleam_stdlib", version = "0.46.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "53940A91251A6BE9AEBB959D46E1CB45B510551D81342A52213850947732D4AB" }, 9 + { name = "gleam_otp", version = "0.16.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "FA0EB761339749B4E82D63016C6A18C4E6662DA05BAB6F1346F9AF2E679E301A" }, 10 + { name = "gleam_stdlib", version = "0.47.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "3B22D46743C46498C8355365243327AC731ECD3959216344FA9CF9AD348620AC" }, 11 11 { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, 12 12 { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, 13 13 { name = "lustre", version = "4.6.3", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "BDF833368F6C8F152F948D5B6A79866E9881CB80CB66C0685B3327E7DCBFB12F" }, ··· 18 18 formz_string = { path = "../formz_string" } 19 19 gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } 20 20 gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 21 - lustre = { version = ">= 4.5.1 and < 5.0.0" } 21 + lustre = { version = ">= 4.6.3 and < 5.0.0" }
+6
formz_lustre/src/formz_lustre/definition.gleam
··· 90 90 let labels_and_values = list.map(variants, fn(s) { #(s, s) }) 91 91 choices_field(labels_and_values, "") 92 92 } 93 + 94 + pub fn make_hidden( 95 + def: formz.Definition(widget.Widget(msg), a, b), 96 + ) -> formz.Definition(widget.Widget(msg), a, b) { 97 + def |> formz.widget(widget.hidden_widget()) 98 + }
+135 -48
formz_lustre/src/formz_lustre/simple.gleam
··· 1 1 import formz 2 2 import formz_lustre/widget 3 + import gleam/int 3 4 import gleam/list 4 - import gleam/string 5 5 import lustre/attribute 6 6 import lustre/element 7 7 import lustre/element/html.{html} ··· 17 17 item: formz.Item(widget.Widget(msg)), 18 18 ) -> element.Element(msg) { 19 19 case item { 20 - formz.Field(field, state, widget.Hidden) -> 21 - html.input([ 22 - attribute.type_("hidden"), 23 - attribute.name(field.name), 24 - attribute.value(state.value), 25 - ]) 20 + formz.Field(config, state, widget.Hidden) -> hidden_input(config, state) 26 21 27 - formz.Field(field, state, widget.Widget(make_widget)) -> { 28 - let id = field.name 22 + formz.ListField(config, states, _, widget.Hidden) -> 23 + states 24 + |> list.map(hidden_input(config, _)) 25 + |> element.fragment 29 26 30 - let label_el = 31 - html.label([attribute.for(id)], [ 32 - html.text(field.label), 33 - html.text(": "), 34 - ]) 35 - 36 - let help_text = case string.is_empty(field.help_text) { 37 - True -> #(element.none(), "") 38 - False -> #( 39 - html.span( 40 - [ 41 - attribute.id(id <> "_help_text"), 42 - attribute.class("formz_help_text"), 43 - ], 44 - [html.text(field.help_text)], 45 - ), 46 - id <> "_help_text", 47 - ) 48 - } 27 + formz.Field(config, state, widget.Widget(make_widget)) -> { 28 + let id = config.name 49 29 50 - let error = case state { 51 - formz.Invalid(error:, ..) -> #( 52 - html.span( 53 - [attribute.id(id <> "_error"), attribute.class("formz_error")], 54 - [html.text(error)], 55 - ), 56 - id <> "_error", 57 - ) 58 - _ -> #(element.none(), "") 59 - } 30 + let label = label(id, config.label) 31 + let help_text = 32 + help_text(id, config.help_text, html.span, "formz_help_text") 33 + let error = error(id, state, html.span, "formz_error") 60 34 61 35 let widget_el = 62 36 make_widget( 63 - field, 37 + config, 64 38 state, 65 39 widget.Args( 66 40 id: id, 67 41 labelled_by: widget.LabelledByLabelFor, 68 42 described_by: widget.DescribedByElementsWithIds([ 69 - help_text.1, 70 - error.1, 43 + help_text.id, 44 + error.id, 71 45 ]), 72 46 ), 73 47 ) 74 48 75 49 html.div([attribute.class("formz_field")], [ 76 - label_el, 50 + label.element, 51 + help_text.element, 77 52 widget_el, 78 - help_text.0, 79 - error.0, 53 + error.element, 54 + ]) 55 + } 56 + 57 + formz.ListField(config, states, _, widget.Widget(make_widget)) -> { 58 + let id = config.name 59 + 60 + let legend = legend(id, config.label) 61 + let help_text = 62 + help_text(id, config.help_text, html.span, "formz_help_text") 63 + 64 + let widgets_el = 65 + states 66 + |> list.index_map(fn(state, i) { 67 + let id = id <> "_" <> int.to_string(i) 68 + let error = error(id, state, html.span, "formz_error") 69 + 70 + let args = 71 + widget.Args( 72 + id, 73 + widget.LabelledByElementsWithIds([legend.id]), 74 + widget.DescribedByElementsWithIds([help_text.id, error.id]), 75 + ) 76 + 77 + html.li([], [make_widget(config, state, args), error.element]) 78 + }) 79 + |> html.ol([], _) 80 + 81 + html.fieldset([attribute.class("formz_listfield")], [ 82 + legend.element, 83 + help_text.element, 84 + widgets_el, 80 85 ]) 81 86 } 82 - formz.SubForm(s, items) -> { 83 - let legend = html.legend([], [html.text(s.label)]) 87 + 88 + formz.SubForm(config, items) -> { 89 + let id = config.name 90 + let legend = legend(id, config.label) 91 + let help_text = help_text(id, config.help_text, html.p, "formz_help_text") 84 92 let children = items |> list.map(generate_item) 85 - html.fieldset([], [legend, html.div([], children)]) 93 + html.fieldset([described_by_attr(help_text.id)], [ 94 + legend.element, 95 + help_text.element, 96 + html.div([], children), 97 + ]) 98 + } 99 + } 100 + } 101 + 102 + fn described_by_attr(id) -> attribute.Attribute(msg) { 103 + case id { 104 + "" -> attribute.none() 105 + _ -> attribute.attribute("aria-describedby", id) 106 + } 107 + } 108 + 109 + pub type ElementAndId(msg) { 110 + ElementAndId(element: element.Element(msg), id: String) 111 + } 112 + 113 + pub fn label(id, label) -> ElementAndId(msg) { 114 + ElementAndId( 115 + html.label([attribute.for(id)], [html.text(label), html.text(": ")]), 116 + "", 117 + ) 118 + } 119 + 120 + pub fn legend(id, label) -> ElementAndId(msg) { 121 + ElementAndId( 122 + html.legend([attribute.id(id <> "_legend")], [html.text(label <> ": ")]), 123 + id <> "_legend", 124 + ) 125 + } 126 + 127 + fn hidden_input( 128 + config: formz.Config, 129 + state: formz.InputState, 130 + ) -> element.Element(msg) { 131 + html.input([ 132 + attribute.type_("hidden"), 133 + attribute.name(config.name), 134 + attribute.value(state.value), 135 + ]) 136 + } 137 + 138 + pub fn help_text( 139 + id: String, 140 + help_text: String, 141 + element: fn(List(attribute.Attribute(msg)), List(element.Element(msg))) -> 142 + element.Element(msg), 143 + class_name: String, 144 + ) -> ElementAndId(msg) { 145 + case help_text { 146 + "" -> ElementAndId(element.none(), "") 147 + _ -> 148 + ElementAndId( 149 + element( 150 + [attribute.id(id <> "_help_text"), attribute.class(class_name)], 151 + [html.text(help_text)], 152 + ), 153 + id <> "_help_text", 154 + ) 155 + } 156 + } 157 + 158 + pub fn error( 159 + id: String, 160 + state: formz.InputState, 161 + element: fn(List(attribute.Attribute(msg)), List(element.Element(msg))) -> 162 + element.Element(msg), 163 + class_name: String, 164 + ) -> ElementAndId(msg) { 165 + case state { 166 + formz.Invalid(error:, ..) -> { 167 + ElementAndId( 168 + element([attribute.id(id <> "_error"), attribute.class(class_name)], [ 169 + html.text(error), 170 + ]), 171 + id <> "_error", 172 + ) 86 173 } 87 - _ -> element.none() 174 + _ -> ElementAndId(element.none(), "") 88 175 } 89 176 }
+20 -21
formz_lustre/src/formz_lustre/widget.gleam
··· 9 9 //// generators, and it's use is optional if you have different needs. 10 10 11 11 import formz 12 - import formz/field.{type Field} 13 12 import gleam/list 14 13 import gleam/string 15 14 import lustre/attribute ··· 17 16 import lustre/element/html 18 17 19 18 pub type Widget(msg) { 20 - Widget(fn(field.Field, formz.InputState, Args) -> element.Element(msg)) 19 + Widget(fn(formz.Config, formz.InputState, Args) -> element.Element(msg)) 21 20 Hidden 22 21 } 23 22 ··· 37 36 /// pointing to this input's id. This has the best accessibility support 38 37 /// and should be [preferred when possible](https://www.w3.org/WAI/tutorials/forms/labels/). 39 38 LabelledByLabelFor 40 - /// The input should be labelled using the `Field`'s `label` field. 39 + /// The input should be labelled using the `formz.Config`'s `label` field. 41 40 LabelledByFieldValue 42 41 /// The input is labelled by elements with the specified ids. 43 42 LabelledByElementsWithIds(ids: List(String)) ··· 134 133 /// Create an `<input type="checkbox">`. The checkbox is checked 135 134 /// if the value is "on" (the browser default). 136 135 pub fn checkbox_widget() { 137 - Widget(fn(field: Field, state: formz.InputState, args: Args) { 136 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 138 137 let value = state.value 139 138 let state = case state { 140 139 formz.Unvalidated(_, requirement) -> formz.Unvalidated("", requirement) 141 140 formz.Valid(_, requirement) -> formz.Valid("", requirement) 142 141 formz.Invalid(_, requirement, e) -> formz.Invalid("", requirement, e) 143 142 } 144 - do_input_widget(field, state, args, "checkbox", [checked_attr(value)]) 143 + do_input_widget(config, state, args, "checkbox", [checked_attr(value)]) 145 144 }) 146 145 } 147 146 ··· 152 151 /// the step size. If you truly need any float, then a `type="text"` input might be a 153 152 /// better choice. 154 153 pub fn number_widget(step_size: String) { 155 - Widget(fn(field: Field, state: formz.InputState, args: Args) { 156 - do_input_widget(field, state, args, "number", [step_size_attr(step_size)]) 154 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 155 + do_input_widget(config, state, args, "number", [step_size_attr(step_size)]) 157 156 }) 158 157 } 159 158 160 159 /// Create an `<input type="password">`. This will not output the value in the 161 160 /// generated HTML for privacy/security concerns. 162 161 pub fn password_widget() { 163 - Widget(fn(field: Field, state: formz.InputState, args: Args) { 162 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 164 163 let state = case state { 165 164 formz.Unvalidated(_, requirement) -> formz.Unvalidated("", requirement) 166 165 formz.Valid(_, requirement) -> formz.Valid("", requirement) 167 166 formz.Invalid(_, requirement, e) -> formz.Invalid("", requirement, e) 168 167 } 169 - do_input_widget(field, state, args, "password", []) 168 + do_input_widget(config, state, args, "password", []) 170 169 }) 171 170 } 172 171 173 172 /// Generate any `<input>` like `type="text"`, `type="email"` or 174 173 /// `type="url"`. 175 174 pub fn input_widget(type_: String) { 176 - Widget(fn(field: Field, state: formz.InputState, args: Args) { 177 - do_input_widget(field, state, args, type_, []) 175 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 176 + do_input_widget(config, state, args, type_, []) 178 177 }) 179 178 } 180 179 181 180 fn do_input_widget( 182 - field: Field, 181 + config: formz.Config, 183 182 state: formz.InputState, 184 183 args: Args, 185 184 type_: String, ··· 189 188 list.flatten([ 190 189 [ 191 190 attribute.type_(type_), 192 - name_attr(field.name), 191 + name_attr(config.name), 193 192 id_attr(args.id), 194 193 required_attr(state.requirement), 195 - disabled_attr(field.disabled), 194 + disabled_attr(config.disabled), 196 195 value_attr(state.value), 197 - aria_label_attr(args.labelled_by, field.label), 196 + aria_label_attr(args.labelled_by, config.label), 198 197 aria_describedby_attr(args.described_by), 199 198 ], 200 199 extra_attrs, ··· 205 204 /// Create a `<textarea></textarea>`. 206 205 pub fn textarea_widget() { 207 206 Widget( 208 - fn(field: Field, state: formz.InputState, args: Args) -> element.Element( 207 + fn(config: formz.Config, state: formz.InputState, args: Args) -> element.Element( 209 208 msg, 210 209 ) { 211 210 html.textarea( 212 211 [ 213 - name_attr(field.name), 212 + name_attr(config.name), 214 213 id_attr(args.id), 215 214 required_attr(state.requirement), 216 - aria_label_attr(args.labelled_by, field.label), 215 + aria_label_attr(args.labelled_by, config.label), 217 216 aria_describedby_attr(args.described_by), 218 217 ], 219 218 state.value, ··· 234 233 /// the second item is the value. 235 234 pub fn select_widget(variants: List(#(String, String))) { 236 235 Widget( 237 - fn(field: Field, state: formz.InputState, args: Args) -> element.Element( 236 + fn(config: formz.Config, state: formz.InputState, args: Args) -> element.Element( 238 237 msg, 239 238 ) { 240 239 html.select( 241 240 [ 242 - name_attr(field.name), 241 + name_attr(config.name), 243 242 id_attr(args.id), 244 243 required_attr(state.requirement), 245 - aria_label_attr(args.labelled_by, field.label), 244 + aria_label_attr(args.labelled_by, config.label), 246 245 aria_describedby_attr(args.described_by), 247 246 ], 248 247 list.flatten([
+126 -18
formz_lustre/test/formz_lustre/simple_test.gleam
··· 1 1 import formz 2 - import formz/field.{field} 3 - import formz/subform 4 2 import formz_lustre/definition 5 3 import formz_lustre/simple 6 4 import formz_string/definition as string_definition ··· 19 17 } 20 18 21 19 pub fn three_field_form() { 22 - use a <- formz.require(field("a"), definition.integer_field()) 23 - use b <- formz.require(field("b"), definition.integer_field()) 24 - use c <- formz.optional(field("c"), definition.integer_field()) 20 + use a <- formz.required_field(formz.named("a"), definition.integer_field()) 21 + use b <- formz.required_field(formz.named("b"), definition.integer_field()) 22 + use c <- formz.field(formz.named("c"), definition.integer_field()) 25 23 26 24 formz.create_form(#(a, b, c)) 27 25 } 28 26 29 27 pub fn three_field_string_form() { 30 - use a <- formz.require(field("a"), string_definition.integer_field()) 31 - use b <- formz.require(field("b"), string_definition.integer_field()) 32 - use c <- formz.optional(field("c"), string_definition.integer_field()) 28 + use a <- formz.required_field( 29 + formz.named("a"), 30 + string_definition.integer_field(), 31 + ) 32 + use b <- formz.required_field( 33 + formz.named("b"), 34 + string_definition.integer_field(), 35 + ) 36 + use c <- formz.field(formz.named("c"), string_definition.integer_field()) 33 37 34 38 formz.create_form(#(a, b, c)) 35 39 } 36 40 37 41 pub fn one_field_and_subform_form() { 38 - use a <- formz.require(field("a"), definition.integer_field()) 39 - use b <- formz.subform(subform.subform("b"), three_field_form()) 42 + use a <- formz.required_field(formz.named("a"), definition.integer_field()) 43 + use b <- formz.subform(formz.named("b"), three_field_form()) 40 44 41 45 formz.create_form(#(a, b)) 42 46 } 43 47 44 48 pub fn one_field_and_subform_string_form() { 45 - use a <- formz.require(field("a"), string_definition.integer_field()) 46 - use b <- formz.subform(subform.subform("b"), three_field_string_form()) 49 + use a <- formz.required_field( 50 + formz.named("a"), 51 + string_definition.integer_field(), 52 + ) 53 + use b <- formz.subform(formz.named("b"), three_field_string_form()) 47 54 48 55 formz.create_form(#(a, b)) 49 56 } 50 57 58 + pub fn hidden_field_form() { 59 + use a <- formz.required_field( 60 + formz.named("a"), 61 + definition.integer_field() |> definition.make_hidden, 62 + ) 63 + 64 + formz.create_form(#(a)) 65 + } 66 + 67 + pub fn hidden_field_string_form() { 68 + use a <- formz.required_field( 69 + formz.named("a"), 70 + string_definition.integer_field() |> string_definition.make_hidden, 71 + ) 72 + 73 + formz.create_form(#(a)) 74 + } 75 + 76 + pub fn list_field_form() { 77 + use a <- formz.limited_list( 78 + formz.limit_between(2, 3), 79 + formz.named("a"), 80 + definition.integer_field(), 81 + ) 82 + formz.create_form(a) 83 + } 84 + 85 + pub fn list_field_string_form() { 86 + use a <- formz.limited_list( 87 + formz.limit_between(2, 3), 88 + formz.named("a"), 89 + string_definition.integer_field(), 90 + ) 91 + formz.create_form(a) 92 + } 93 + 51 94 pub fn three_field_form_test() { 52 95 let html = three_field_form() |> simple.generate |> convert_to_string 53 96 let string_html = three_field_string_form() |> string_simple.generate ··· 70 113 pub fn three_field_form_with_help_test() { 71 114 let html = 72 115 three_field_form() 73 - |> formz.update_field("b", field.set_help_text(_, "this is field b")) 116 + |> formz.update_config("b", formz.set_help_text(_, "this is field b")) 74 117 |> simple.generate 75 118 |> convert_to_string 76 119 let string_html = 77 120 three_field_string_form() 78 - |> formz.update_field("b", field.set_help_text(_, "this is field b")) 121 + |> formz.update_config("b", formz.set_help_text(_, "this is field b")) 79 122 |> string_simple.generate 80 123 html |> should.equal(string_html) 81 124 } ··· 100 143 three_field_form() 101 144 |> formz.data([#("a", "x"), #("b", "x"), #("c", "x")]) 102 145 |> formz.validate(["b"]) 103 - |> formz.update_field("b", field.set_help_text(_, "this is field b")) 146 + |> formz.update_config("b", formz.set_help_text(_, "this is field b")) 104 147 |> simple.generate 105 148 |> convert_to_string 106 149 let string_html = 107 150 three_field_string_form() 108 151 |> formz.data([#("a", "x"), #("b", "x"), #("c", "x")]) 109 152 |> formz.validate(["b"]) 110 - |> formz.update_field("b", field.set_help_text(_, "this is field b")) 153 + |> formz.update_config("b", formz.set_help_text(_, "this is field b")) 111 154 |> string_simple.generate 112 155 html |> should.equal(string_html) 113 156 } ··· 115 158 pub fn three_field_form_with_disabled_test() { 116 159 let html = 117 160 three_field_form() 118 - |> formz.update_field("b", field.set_disabled(_, True)) 161 + |> formz.update_config("b", formz.set_disabled(_, True)) 119 162 |> simple.generate 120 163 |> convert_to_string 121 164 let string_html = 122 165 three_field_string_form() 123 - |> formz.update_field("b", field.set_disabled(_, True)) 166 + |> formz.update_config("b", formz.set_disabled(_, True)) 124 167 |> string_simple.generate 125 168 html |> should.equal(string_html) 126 169 } ··· 135 178 |> string_simple.generate 136 179 html |> should.equal(string_html) 137 180 } 181 + 182 + pub fn subform_with_help_text_test() { 183 + let html = 184 + one_field_and_subform_form() 185 + |> formz.update_config("b", formz.set_help_text(_, "this is subform b")) 186 + |> simple.generate 187 + |> convert_to_string 188 + let string_html = 189 + one_field_and_subform_string_form() 190 + |> formz.update_config("b", formz.set_help_text(_, "this is subform b")) 191 + |> string_simple.generate 192 + html |> should.equal(string_html) 193 + } 194 + 195 + pub fn subform_with_help_text_and_error_test() { 196 + let html = 197 + one_field_and_subform_form() 198 + |> formz.update_config("b", formz.set_help_text(_, "this is subform b")) 199 + |> formz.field_error("b.a", "woops") 200 + |> simple.generate 201 + |> convert_to_string 202 + let string_html = 203 + one_field_and_subform_string_form() 204 + |> formz.update_config("b", formz.set_help_text(_, "this is subform b")) 205 + |> formz.field_error("b.a", "woops") 206 + |> string_simple.generate 207 + html |> should.equal(string_html) 208 + } 209 + 210 + pub fn hidden_field_form_test() { 211 + let html = 212 + hidden_field_form() 213 + |> simple.generate 214 + |> convert_to_string 215 + let string_html = 216 + hidden_field_string_form() 217 + |> string_simple.generate 218 + html |> should.equal(string_html) 219 + } 220 + 221 + pub fn list_field_test() { 222 + let html = 223 + list_field_form() 224 + |> simple.generate 225 + |> convert_to_string 226 + let string_html = 227 + list_field_string_form() 228 + |> string_simple.generate 229 + html |> should.equal(string_html) 230 + } 231 + 232 + pub fn list_field_with_error_test() { 233 + let html = 234 + list_field_form() 235 + |> formz.validate_all 236 + |> formz.listfield_errors("a", [Ok(Nil), Error("woops")]) 237 + |> simple.generate 238 + |> convert_to_string 239 + let string_html = 240 + list_field_string_form() 241 + |> formz.validate_all 242 + |> formz.listfield_errors("a", [Ok(Nil), Error("woops")]) 243 + |> string_simple.generate 244 + html |> should.equal(string_html) 245 + }
+4 -5
formz_lustre/test/formz_lustre/widgets_test.gleam
··· 1 1 import formz 2 - import formz/field 3 2 import formz_lustre/widget 4 3 import formz_string/widget as string_widget 5 4 import gleeunit ··· 43 42 string string_widget: string_widget.Widget, 44 43 widget widget: widget.Widget(msg), 45 44 ) { 46 - let string_field = field.Field(name:, label:, help_text:, disabled:) 47 - let field = field.Field(name:, label:, help_text:, disabled:) 45 + let string_config = formz.Config(name:, label:, help_text:, disabled:) 46 + let config = formz.Config(name:, label:, help_text:, disabled:) 48 47 49 48 let requirement = case required { 50 49 True -> formz.Required ··· 55 54 case widget, string_widget { 56 55 widget.Hidden, string_widget.Hidden -> Nil 57 56 widget.Widget(make_widget), string_widget.Widget(make_string_widget) -> 58 - make_widget(field, state, args) 57 + make_widget(config, state, args) 59 58 |> convert_to_string 60 59 |> should.equal(make_string_widget( 61 - string_field, 60 + string_config, 62 61 state, 63 62 args |> to_string_args, 64 63 ))
+1 -1
formz_nakai/manifest.toml
··· 4 4 packages = [ 5 5 { name = "formz", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "justin"], source = "local", path = "../formz" }, 6 6 { name = "formz_string", version = "1.0.0", build_tools = ["gleam"], requirements = ["formz", "gleam_stdlib"], source = "local", path = "../formz_string" }, 7 - { name = "gleam_stdlib", version = "0.45.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "206FCE1A76974AECFC55AEBCD0217D59EDE4E408C016E2CFCCC8FF51278F186E" }, 7 + { name = "gleam_stdlib", version = "0.47.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "3B22D46743C46498C8355365243327AC731ECD3959216344FA9CF9AD348620AC" }, 8 8 { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, 9 9 { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, 10 10 { name = "nakai", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "nakai", source = "hex", outer_checksum = "58D863966CFCAFCE60CE67C609B2C803551F76FEED8F3BAF6CD03F3C4B16C954" },
+6
formz_nakai/src/formz_nakai/definition.gleam
··· 90 90 let labels_and_values = list.map(variants, fn(s) { #(s, s) }) 91 91 choices_field(labels_and_values, "") 92 92 } 93 + 94 + pub fn make_hidden( 95 + def: formz.Definition(widget.Widget, a, b), 96 + ) -> formz.Definition(widget.Widget, a, b) { 97 + def |> formz.widget(widget.hidden_widget()) 98 + }
+130 -42
formz_nakai/src/formz_nakai/simple.gleam
··· 1 1 import formz 2 2 import formz_nakai/widget 3 + import gleam/int 3 4 import gleam/list 4 5 import nakai/attr 5 6 import nakai/html ··· 7 8 pub fn generate(form: formz.Form(widget.Widget, a)) -> html.Node { 8 9 form 9 10 |> formz.items 10 - |> list.map(generate_visible_item) 11 + |> list.map(generate_item) 11 12 |> html.div([attr.class("formz_items")], _) 12 13 } 13 14 14 - pub fn generate_visible_item(item: formz.Item(widget.Widget)) -> html.Node { 15 + pub fn generate_item(item: formz.Item(widget.Widget)) -> html.Node { 15 16 case item { 16 - formz.Field(field, state, widget.Hidden) -> 17 - html.input([ 18 - attr.type_("hidden"), 19 - attr.name(field.name), 20 - attr.value(state.value), 21 - ]) 22 - formz.Field(field, state, widget.Widget(make_widget)) -> { 23 - let id = field.name 17 + formz.Field(config, state, widget.Hidden) -> hidden_input(config, state) 24 18 25 - let label_el = 26 - html.label([attr.for(id)], [html.Text(field.label), html.Text(": ")]) 19 + formz.ListField(config, states, _, widget.Hidden) -> 20 + states |> list.map(hidden_input(config, _)) |> html.Fragment 27 21 28 - let help_text = case field.help_text { 29 - "" -> #(html.Nothing, "") 30 - _ -> #( 31 - html.span( 32 - [attr.id(id <> "_help_text"), attr.class("formz_help_text")], 33 - [html.Text(field.help_text)], 34 - ), 35 - id <> "_help_text", 36 - ) 37 - } 38 - let error = case state { 39 - formz.Invalid(error:, ..) -> #( 40 - html.span([attr.id(id <> "_error"), attr.class("formz_error")], [ 41 - html.Text(error), 42 - ]), 43 - id <> "_error", 44 - ) 45 - _ -> #(html.Nothing, "") 46 - } 22 + formz.Field(config, state, widget.Widget(make_widget)) -> { 23 + let id = config.name 24 + 25 + let label = label(id, config.label) 26 + let help_text = 27 + help_text(id, config.help_text, html.span, "formz_help_text") 28 + let error = error(id, state, html.span, "formz_error") 47 29 48 30 let widget_el = 49 31 make_widget( 50 - field, 32 + config, 51 33 state, 52 34 widget.Args( 53 35 id: id, 54 36 labelled_by: widget.LabelledByLabelFor, 55 37 described_by: widget.DescribedByElementsWithIds([ 56 - help_text.1, 57 - error.1, 38 + help_text.id, 39 + error.id, 58 40 ]), 59 41 ), 60 42 ) 61 43 62 44 html.div([attr.class("formz_field")], [ 63 - label_el, 45 + label.element, 46 + help_text.element, 64 47 widget_el, 65 - help_text.0, 66 - error.0, 48 + error.element, 67 49 ]) 68 50 } 69 - formz.SubForm(s, items) -> { 70 - let legend = html.legend([], [html.Text(s.label)]) 71 - let children = items |> list.map(generate_visible_item) 72 - html.fieldset([], [legend, html.div([], children)]) 51 + 52 + formz.ListField(config, states, _, widget.Widget(make_widget)) -> { 53 + let id = config.name 54 + 55 + let legend = legend(id, config.label) 56 + let help_text = 57 + help_text(id, config.help_text, html.span, "formz_help_text") 58 + 59 + let widgets_el = 60 + states 61 + |> list.index_map(fn(state, i) { 62 + let id = id <> "_" <> int.to_string(i) 63 + let error = error(id, state, html.span, "formz_error") 64 + 65 + let args = 66 + widget.Args( 67 + id, 68 + widget.LabelledByElementsWithIds([legend.id]), 69 + widget.DescribedByElementsWithIds([help_text.id, error.id]), 70 + ) 71 + 72 + html.li([], [make_widget(config, state, args), error.element]) 73 + }) 74 + |> html.ol([], _) 75 + 76 + html.fieldset([attr.class("formz_listfield")], [ 77 + legend.element, 78 + help_text.element, 79 + widgets_el, 80 + ]) 73 81 } 74 82 75 - _ -> html.Nothing 83 + formz.SubForm(config, items) -> { 84 + let id = config.name 85 + let legend = legend(id, config.label) 86 + let help_text = help_text(id, config.help_text, html.p, "formz_help_text") 87 + let children = items |> list.map(generate_item) 88 + html.fieldset(list.flatten([described_by_attr(help_text.id)]), [ 89 + legend.element, 90 + help_text.element, 91 + html.div([], children), 92 + ]) 93 + } 94 + } 95 + } 96 + 97 + fn described_by_attr(id) -> List(attr.Attr) { 98 + case id { 99 + "" -> [] 100 + _ -> [attr.Attr("aria-describedby", id)] 101 + } 102 + } 103 + 104 + pub type ElementAndId { 105 + ElementAndId(element: html.Node, id: String) 106 + } 107 + 108 + pub fn label(id, label) -> ElementAndId { 109 + ElementAndId( 110 + html.label([attr.for(id)], [html.Text(label), html.Text(": ")]), 111 + "", 112 + ) 113 + } 114 + 115 + pub fn legend(id, label) -> ElementAndId { 116 + ElementAndId( 117 + html.legend([attr.id(id <> "_legend")], [html.Text(label <> ": ")]), 118 + id <> "_legend", 119 + ) 120 + } 121 + 122 + fn hidden_input(config: formz.Config, state: formz.InputState) -> html.Node { 123 + html.input([ 124 + attr.type_("hidden"), 125 + attr.name(config.name), 126 + attr.value(state.value), 127 + ]) 128 + } 129 + 130 + pub fn help_text( 131 + id: String, 132 + help_text: String, 133 + element: fn(List(attr.Attr), List(html.Node)) -> html.Node, 134 + class_name: String, 135 + ) -> ElementAndId { 136 + case help_text { 137 + "" -> ElementAndId(html.Nothing, "") 138 + _ -> 139 + ElementAndId( 140 + element([attr.id(id <> "_help_text"), attr.class(class_name)], [ 141 + html.Text(help_text), 142 + ]), 143 + id <> "_help_text", 144 + ) 145 + } 146 + } 147 + 148 + pub fn error( 149 + id: String, 150 + state: formz.InputState, 151 + element: fn(List(attr.Attr), List(html.Node)) -> html.Node, 152 + class_name: String, 153 + ) -> ElementAndId { 154 + case state { 155 + formz.Invalid(error:, ..) -> { 156 + ElementAndId( 157 + element([attr.id(id <> "_error"), attr.class(class_name)], [ 158 + html.Text(error), 159 + ]), 160 + id <> "_error", 161 + ) 162 + } 163 + _ -> ElementAndId(html.Nothing, "") 76 164 } 77 165 }
+52 -49
formz_nakai/src/formz_nakai/widget.gleam
··· 9 9 //// generators, and it's use is optional if you have different needs. 10 10 11 11 import formz 12 - import formz/field.{type Field, Field} 13 12 import gleam/list 14 13 import gleam/string 15 14 import nakai/attr 16 15 import nakai/html 17 16 18 17 pub type Widget { 19 - Widget(fn(field.Field, formz.InputState, Args) -> html.Node) 18 + Widget(fn(formz.Config, formz.InputState, Args) -> html.Node) 20 19 Hidden 21 20 } 22 21 ··· 36 35 /// pointing to this input's id. This has the best accessibility support 37 36 /// and should be [preferred when possible](https://www.w3.org/WAI/tutorials/forms/labels/). 38 37 LabelledByLabelFor 39 - /// The input should be labelled using the `Field`'s `label` field. 38 + /// The input should be labelled using the `formz.Config`'s `label`. 40 39 LabelledByFieldValue 41 40 /// The input is labelled by elements with the specified ids. 42 41 LabelledByElementsWithIds(ids: List(String)) ··· 132 131 /// Create an `<input type="checkbox">`. The checkbox is checked 133 132 /// if the value is "on" (the browser default). 134 133 pub fn checkbox_widget() -> Widget { 135 - Widget(fn(field: Field, state: formz.InputState, args: Args) { 134 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 136 135 let value = state.value 137 136 let state = case state { 138 137 formz.Unvalidated(_, requirement) -> formz.Unvalidated("", requirement) 139 138 formz.Valid(_, requirement) -> formz.Valid("", requirement) 140 139 formz.Invalid(_, requirement, e) -> formz.Invalid("", requirement, e) 141 140 } 142 - do_input_widget(field, state, args, "checkbox", [checked_attr(value)]) 141 + do_input_widget(config, state, args, "checkbox", [checked_attr(value)]) 143 142 }) 144 143 } 145 144 ··· 150 149 /// the step size. If you truly need any float, then a `type="text"` input might be a 151 150 /// better choice. 152 151 pub fn number_widget(step_size: String) { 153 - Widget(fn(field: Field, state: formz.InputState, args: Args) { 154 - do_input_widget(field, state, args, "number", [step_size_attr(step_size)]) 152 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 153 + do_input_widget(config, state, args, "number", [step_size_attr(step_size)]) 155 154 }) 156 155 } 157 156 158 157 /// Create an `<input type="password">`. This will not output the value in the 159 158 /// generated HTML for privacy/security concerns. 160 159 pub fn password_widget() { 161 - Widget(fn(field: Field, state: formz.InputState, args: Args) { 160 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 162 161 let state = case state { 163 162 formz.Unvalidated(_, requirement) -> formz.Unvalidated("", requirement) 164 163 formz.Valid(_, requirement) -> formz.Valid("", requirement) 165 164 formz.Invalid(_, requirement, e) -> formz.Invalid("", requirement, e) 166 165 } 167 - do_input_widget(field, state, args, "password", []) 166 + do_input_widget(config, state, args, "password", []) 168 167 }) 169 168 } 170 169 171 170 /// Generate any `<input>` like `type="text"`, `type="email"` or 172 171 /// `type="url"`. 173 172 pub fn input_widget(type_: String) { 174 - Widget(fn(field: Field, state: formz.InputState, args: Args) { 175 - do_input_widget(field, state, args, type_, []) 173 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 174 + do_input_widget(config, state, args, type_, []) 176 175 }) 177 176 } 178 177 179 178 fn do_input_widget( 180 - field: Field, 179 + config: formz.Config, 181 180 state: formz.InputState, 182 181 args: Args, 183 182 type_: String, ··· 186 185 html.input( 187 186 list.flatten([ 188 187 type_attr(type_), 189 - name_attr(field.name), 188 + name_attr(config.name), 190 189 id_attr(args.id), 191 190 required_attr(state.requirement), 192 191 value_attr(state.value), 193 - disabled_attr(field.disabled), 192 + disabled_attr(config.disabled), 193 + aria_label_attr(args.labelled_by, config.label), 194 194 aria_describedby_attr(args.described_by), 195 - aria_label_attr(args.labelled_by, field.label), 196 195 extra_attrs |> list.flatten, 197 196 ]), 198 197 ) ··· 200 199 201 200 /// Create a `<textarea></textarea>`. 202 201 pub fn textarea_widget() { 203 - Widget(fn(field: Field, state: formz.InputState, args: Args) -> html.Node { 204 - html.textarea( 205 - list.flatten([ 206 - name_attr(field.name), 207 - id_attr(args.id), 208 - required_attr(state.requirement), 209 - aria_label_attr(args.labelled_by, field.label), 210 - ]), 211 - [html.Text(state.value)], 212 - ) 213 - }) 202 + Widget( 203 + fn(config: formz.Config, state: formz.InputState, args: Args) -> html.Node { 204 + html.textarea( 205 + list.flatten([ 206 + name_attr(config.name), 207 + id_attr(args.id), 208 + required_attr(state.requirement), 209 + aria_label_attr(args.labelled_by, config.label), 210 + ]), 211 + [html.Text(state.value)], 212 + ) 213 + }, 214 + ) 214 215 } 215 216 216 217 /// Create a `<input type="hidden">`. This is useful for if a field is just ··· 224 225 /// of variants is a two-tuple, where the first item is the text to display and 225 226 /// the second item is the value. 226 227 pub fn select_widget(variants: List(#(String, String))) { 227 - Widget(fn(field: Field, state: formz.InputState, args: Args) -> html.Node { 228 - html.select( 229 - list.flatten([ 230 - name_attr(field.name), 231 - id_attr(args.id), 232 - required_attr(state.requirement), 233 - aria_label_attr(args.labelled_by, field.label), 234 - ]), 235 - list.flatten([ 236 - [html.option([attr.value("")], [html.Text("Select...")]), html.hr([])], 237 - list.map(variants, fn(variant) { 238 - let val = variant.1 239 - let selected_attr = case state.value == val { 240 - True -> [attr.selected()] 241 - _ -> [] 242 - } 243 - html.option(list.flatten([value_attr(val), selected_attr]), [ 244 - html.Text(variant.0), 245 - ]) 246 - }), 247 - ]), 248 - ) 249 - }) 228 + Widget( 229 + fn(config: formz.Config, state: formz.InputState, args: Args) -> html.Node { 230 + html.select( 231 + list.flatten([ 232 + name_attr(config.name), 233 + id_attr(args.id), 234 + required_attr(state.requirement), 235 + aria_label_attr(args.labelled_by, config.label), 236 + ]), 237 + list.flatten([ 238 + [html.option([attr.value("")], [html.Text("Select...")]), html.hr([])], 239 + list.map(variants, fn(variant) { 240 + let val = variant.1 241 + let selected_attr = case state.value == val { 242 + True -> [attr.selected()] 243 + _ -> [] 244 + } 245 + html.option(list.flatten([value_attr(val), selected_attr]), [ 246 + html.Text(variant.0), 247 + ]) 248 + }), 249 + ]), 250 + ) 251 + }, 252 + ) 250 253 }
+126 -18
formz_nakai/test/formz_nakai/simple_test.gleam
··· 1 1 import formz 2 - import formz/field.{field} 3 - import formz/subform 4 2 import formz_nakai/definition 5 3 import formz_nakai/simple 6 4 import formz_string/definition as string_definition ··· 37 35 } 38 36 39 37 pub fn three_field_form() { 40 - use a <- formz.require(field("a"), definition.integer_field()) 41 - use b <- formz.require(field("b"), definition.integer_field()) 42 - use c <- formz.optional(field("c"), definition.integer_field()) 38 + use a <- formz.required_field(formz.named("a"), definition.integer_field()) 39 + use b <- formz.required_field(formz.named("b"), definition.integer_field()) 40 + use c <- formz.field(formz.named("c"), definition.integer_field()) 43 41 44 42 formz.create_form(#(a, b, c)) 45 43 } 46 44 47 45 pub fn three_field_string_form() { 48 - use a <- formz.require(field("a"), string_definition.integer_field()) 49 - use b <- formz.require(field("b"), string_definition.integer_field()) 50 - use c <- formz.optional(field("c"), string_definition.integer_field()) 46 + use a <- formz.required_field( 47 + formz.named("a"), 48 + string_definition.integer_field(), 49 + ) 50 + use b <- formz.required_field( 51 + formz.named("b"), 52 + string_definition.integer_field(), 53 + ) 54 + use c <- formz.field(formz.named("c"), string_definition.integer_field()) 51 55 52 56 formz.create_form(#(a, b, c)) 53 57 } 54 58 55 59 pub fn one_field_and_subform_form() { 56 - use a <- formz.require(field("a"), definition.integer_field()) 57 - use b <- formz.subform(subform.subform("b"), three_field_form()) 60 + use a <- formz.required_field(formz.named("a"), definition.integer_field()) 61 + use b <- formz.subform(formz.named("b"), three_field_form()) 58 62 59 63 formz.create_form(#(a, b)) 60 64 } 61 65 62 66 pub fn one_field_and_subform_string_form() { 63 - use a <- formz.require(field("a"), string_definition.integer_field()) 64 - use b <- formz.subform(subform.subform("b"), three_field_string_form()) 67 + use a <- formz.required_field( 68 + formz.named("a"), 69 + string_definition.integer_field(), 70 + ) 71 + use b <- formz.subform(formz.named("b"), three_field_string_form()) 65 72 66 73 formz.create_form(#(a, b)) 67 74 } 68 75 76 + pub fn hidden_field_form() { 77 + use a <- formz.required_field( 78 + formz.named("a"), 79 + definition.integer_field() |> definition.make_hidden, 80 + ) 81 + 82 + formz.create_form(#(a)) 83 + } 84 + 85 + pub fn hidden_field_string_form() { 86 + use a <- formz.required_field( 87 + formz.named("a"), 88 + string_definition.integer_field() |> string_definition.make_hidden, 89 + ) 90 + 91 + formz.create_form(#(a)) 92 + } 93 + 94 + pub fn list_field_form() { 95 + use a <- formz.limited_list( 96 + formz.limit_between(2, 3), 97 + formz.named("a"), 98 + definition.integer_field(), 99 + ) 100 + formz.create_form(a) 101 + } 102 + 103 + pub fn list_field_string_form() { 104 + use a <- formz.limited_list( 105 + formz.limit_between(2, 3), 106 + formz.named("a"), 107 + string_definition.integer_field(), 108 + ) 109 + formz.create_form(a) 110 + } 111 + 69 112 pub fn three_field_form_test() { 70 113 let html = three_field_form() |> simple.generate |> convert_to_string 71 114 let string_html = three_field_string_form() |> string_simple.generate ··· 88 131 pub fn three_field_form_with_help_test() { 89 132 let html = 90 133 three_field_form() 91 - |> formz.update_field("b", field.set_help_text(_, "this is field b")) 134 + |> formz.update_config("b", formz.set_help_text(_, "this is field b")) 92 135 |> simple.generate 93 136 |> convert_to_string 94 137 let string_html = 95 138 three_field_string_form() 96 - |> formz.update_field("b", field.set_help_text(_, "this is field b")) 139 + |> formz.update_config("b", formz.set_help_text(_, "this is field b")) 97 140 |> string_simple.generate 98 141 html |> should.equal(string_html) 99 142 } ··· 118 161 three_field_form() 119 162 |> formz.data([#("a", "x"), #("b", "x"), #("c", "x")]) 120 163 |> formz.validate(["b"]) 121 - |> formz.update_field("b", field.set_help_text(_, "this is field b")) 164 + |> formz.update_config("b", formz.set_help_text(_, "this is field b")) 122 165 |> simple.generate 123 166 |> convert_to_string 124 167 let string_html = 125 168 three_field_string_form() 126 169 |> formz.data([#("a", "x"), #("b", "x"), #("c", "x")]) 127 170 |> formz.validate(["b"]) 128 - |> formz.update_field("b", field.set_help_text(_, "this is field b")) 171 + |> formz.update_config("b", formz.set_help_text(_, "this is field b")) 129 172 |> string_simple.generate 130 173 html |> should.equal(string_html) 131 174 } ··· 133 176 pub fn three_field_form_with_disabled_test() { 134 177 let html = 135 178 three_field_form() 136 - |> formz.update_field("b", field.set_disabled(_, True)) 179 + |> formz.update_config("b", formz.set_disabled(_, True)) 137 180 |> simple.generate 138 181 |> convert_to_string 139 182 let string_html = 140 183 three_field_string_form() 141 - |> formz.update_field("b", field.set_disabled(_, True)) 184 + |> formz.update_config("b", formz.set_disabled(_, True)) 142 185 |> string_simple.generate 143 186 html |> should.equal(string_html) 144 187 } ··· 153 196 |> string_simple.generate 154 197 html |> should.equal(string_html) 155 198 } 199 + 200 + pub fn subform_with_help_text_test() { 201 + let html = 202 + one_field_and_subform_form() 203 + |> formz.update_config("b", formz.set_help_text(_, "this is subform b")) 204 + |> simple.generate 205 + |> convert_to_string 206 + let string_html = 207 + one_field_and_subform_string_form() 208 + |> formz.update_config("b", formz.set_help_text(_, "this is subform b")) 209 + |> string_simple.generate 210 + html |> should.equal(string_html) 211 + } 212 + 213 + pub fn subform_with_help_text_and_error_test() { 214 + let html = 215 + one_field_and_subform_form() 216 + |> formz.update_config("b", formz.set_help_text(_, "this is subform b")) 217 + |> formz.field_error("b.a", "woops") 218 + |> simple.generate 219 + |> convert_to_string 220 + let string_html = 221 + one_field_and_subform_string_form() 222 + |> formz.update_config("b", formz.set_help_text(_, "this is subform b")) 223 + |> formz.field_error("b.a", "woops") 224 + |> string_simple.generate 225 + html |> should.equal(string_html) 226 + } 227 + 228 + pub fn hidden_field_form_test() { 229 + let html = 230 + hidden_field_form() 231 + |> simple.generate 232 + |> convert_to_string 233 + let string_html = 234 + hidden_field_string_form() 235 + |> string_simple.generate 236 + html |> should.equal(string_html) 237 + } 238 + 239 + pub fn list_field_test() { 240 + let html = 241 + list_field_form() 242 + |> simple.generate 243 + |> convert_to_string 244 + let string_html = 245 + list_field_string_form() 246 + |> string_simple.generate 247 + html |> should.equal(string_html) 248 + } 249 + 250 + pub fn list_field_with_error_test() { 251 + let html = 252 + list_field_form() 253 + |> formz.validate_all 254 + |> formz.listfield_errors("a", [Ok(Nil), Error("woops")]) 255 + |> simple.generate 256 + |> convert_to_string 257 + let string_html = 258 + list_field_string_form() 259 + |> formz.validate_all 260 + |> formz.listfield_errors("a", [Ok(Nil), Error("woops")]) 261 + |> string_simple.generate 262 + html |> should.equal(string_html) 263 + }
+4 -5
formz_nakai/test/formz_nakai/widgets_test.gleam
··· 1 1 import formz 2 - import formz/field 3 2 import formz_nakai/widget 4 3 import formz_string/widget as string_widget 5 4 import gleam/string ··· 61 60 string string_widget: string_widget.Widget, 62 61 widget widget: widget.Widget, 63 62 ) { 64 - let string_field = field.Field(name:, label:, help_text:, disabled:) 65 - let field = field.Field(name:, label:, help_text:, disabled:) 63 + let string_config = formz.Config(name:, label:, help_text:, disabled:) 64 + let config = formz.Config(name:, label:, help_text:, disabled:) 66 65 67 66 let requirement = case required { 68 67 True -> formz.Required ··· 73 72 case widget, string_widget { 74 73 widget.Hidden, string_widget.Hidden -> Nil 75 74 widget.Widget(make_widget), string_widget.Widget(make_string_widget) -> 76 - make_widget(field, state, args) 75 + make_widget(config, state, args) 77 76 |> convert_to_string 78 77 |> should.equal(make_string_widget( 79 - string_field, 78 + string_config, 80 79 state, 81 80 args |> to_string_args, 82 81 ))
+2 -2
formz_string/birdie_snapshots/hidden_field_form.accepted
··· 1 1 --- 2 - version: 1.2.3 2 + version: 1.2.4 3 3 title: hidden field form 4 4 file: ./test/formz_string/simple_test.gleam 5 5 test_name: hidden_field_form_test 6 6 --- 7 - <div class="formz_items"><input type="hidden" name="a" value=""></div> 7 + <div class="formz_items"><input type="hidden" name="a" value="1"></div>
+7
formz_string/birdie_snapshots/hidden_field_form_no_value.accepted
··· 1 + --- 2 + version: 1.2.4 3 + title: hidden field form no value 4 + file: ./test/formz_string/simple_test.gleam 5 + test_name: hidden_field_form_no_value_test 6 + --- 7 + <div class="formz_items"><input type="hidden" name="a" value></div>
+7
formz_string/birdie_snapshots/list_field_form.accepted
··· 1 + --- 2 + version: 1.2.4 3 + title: list field form 4 + file: ./test/formz_string/simple_test.gleam 5 + test_name: list_field_test 6 + --- 7 + <div class="formz_items"><fieldset class="formz_listfield"><legend id="a_legend">A: </legend><ol><li><input type="number" name="a" id="a_0" required aria-labelledby="a_legend"></li><li><input type="number" name="a" id="a_1" required aria-labelledby="a_legend"></li></ol></fieldset></div>
+7
formz_string/birdie_snapshots/list_field_form_hidden.accepted
··· 1 + --- 2 + version: 1.2.4 3 + title: list field form hidden 4 + file: ./test/formz_string/simple_test.gleam 5 + test_name: list_field_hidden_test 6 + --- 7 + <div class="formz_items"><input type="hidden" name="a" value="1"><input type="hidden" name="a" value="2"><input type="hidden" name="a" value></div>
+7
formz_string/birdie_snapshots/list_field_form_with_error.accepted
··· 1 + --- 2 + version: 1.2.4 3 + title: list field form with error 4 + file: ./test/formz_string/simple_test.gleam 5 + test_name: list_field_with_error_test 6 + --- 7 + <div class="formz_items"><fieldset class="formz_listfield"><legend id="a_legend">A: </legend><ol><li><input type="number" name="a" id="a_0" required aria-labelledby="a_legend" aria-describedby="a_0_error"><span id="a_0_error" class="formz_error">must be a whole number</span></li><li><input type="number" name="a" id="a_1" required aria-labelledby="a_legend" aria-describedby="a_1_error"><span id="a_1_error" class="formz_error">woops</span></li></ol></fieldset></div>
+2 -2
formz_string/birdie_snapshots/subform.accepted
··· 1 1 --- 2 - version: 1.2.3 2 + version: 1.2.4 3 3 title: subform 4 4 file: ./test/formz_string/simple_test.gleam 5 5 test_name: subform_test 6 6 --- 7 - <div class="formz_items"><div class="formz_field"><label for="a">A: </label><input type="number" name="a" id="a" required></div><fieldset><legend>B</legend><div><div class="formz_field"><label for="b.a">A: </label><input type="number" name="b.a" id="b.a" required></div><div class="formz_field"><label for="b.b">B: </label><input type="number" name="b.b" id="b.b" required></div><div class="formz_field"><label for="b.c">C: </label><input type="number" name="b.c" id="b.c"></div></div></fieldset></div> 7 + <div class="formz_items"><div class="formz_field"><label for="a">A: </label><input type="number" name="a" id="a" required></div><fieldset><legend id="b_legend">B: </legend><div><div class="formz_field"><label for="b.a">A: </label><input type="number" name="b.a" id="b.a" required></div><div class="formz_field"><label for="b.b">B: </label><input type="number" name="b.b" id="b.b" required></div><div class="formz_field"><label for="b.c">C: </label><input type="number" name="b.c" id="b.c"></div></div></fieldset></div>
+2 -2
formz_string/birdie_snapshots/subform_with_help_text.accepted
··· 1 1 --- 2 - version: 1.2.3 2 + version: 1.2.4 3 3 title: subform with help text 4 4 file: ./test/formz_string/simple_test.gleam 5 5 test_name: subform_with_help_text_test 6 6 --- 7 - <div class="formz_items"><div class="formz_field"><label for="a">A: </label><input type="number" name="a" id="a" required></div><fieldset aria-describedby="b_help_text"><legend>B</legend><p id="b_help_text" class="formz_help_text">this is subform b</p><div><div class="formz_field"><label for="b.a">A: </label><input type="number" name="b.a" id="b.a" required></div><div class="formz_field"><label for="b.b">B: </label><input type="number" name="b.b" id="b.b" required></div><div class="formz_field"><label for="b.c">C: </label><input type="number" name="b.c" id="b.c"></div></div></fieldset></div> 7 + <div class="formz_items"><div class="formz_field"><label for="a">A: </label><input type="number" name="a" id="a" required></div><fieldset aria-describedby="b_help_text"><legend id="b_legend">B: </legend><p id="b_help_text" class="formz_help_text">this is subform b</p><div><div class="formz_field"><label for="b.a">A: </label><input type="number" name="b.a" id="b.a" required></div><div class="formz_field"><label for="b.b">B: </label><input type="number" name="b.b" id="b.b" required></div><div class="formz_field"><label for="b.c">C: </label><input type="number" name="b.c" id="b.c"></div></div></fieldset></div>
+7
formz_string/birdie_snapshots/subform_with_help_text_and_error.accepted
··· 1 + --- 2 + version: 1.2.4 3 + title: subform with help text and error 4 + file: ./test/formz_string/simple_test.gleam 5 + test_name: subform_with_help_text_and_error_test 6 + --- 7 + <div class="formz_items"><div class="formz_field"><label for="a">A: </label><input type="number" name="a" id="a" required></div><fieldset aria-describedby="b_help_text"><legend id="b_legend">B: </legend><p id="b_help_text" class="formz_help_text">this is subform b</p><div><div class="formz_field"><label for="b.a">A: </label><input type="number" name="b.a" id="b.a" required aria-describedby="b.a_error"><span id="b.a_error" class="formz_error">woops</span></div><div class="formz_field"><label for="b.b">B: </label><input type="number" name="b.b" id="b.b" required></div><div class="formz_field"><label for="b.c">C: </label><input type="number" name="b.c" id="b.c"></div></div></fieldset></div>
+2 -2
formz_string/birdie_snapshots/three_field_form_with_error_and_help_text.accepted
··· 1 1 --- 2 - version: 1.2.3 2 + version: 1.2.4 3 3 title: three field form with error and help text 4 4 file: ./test/formz_string/simple_test.gleam 5 5 test_name: three_field_form_with_error_and_help_test 6 6 --- 7 - <div class="formz_items"><div class="formz_field"><label for="a">A: </label><input type="number" name="a" id="a" required value="x"></div><div class="formz_field"><label for="b">B: </label><input type="number" name="b" id="b" required value="x" aria-describedby="b_help_text b_error"><span id="b_help_text" class="formz_help_text">this is field b</span><span id="b_error" class="formz_error">must be a whole number</span></div><div class="formz_field"><label for="c">C: </label><input type="number" name="c" id="c" value="x"></div></div> 7 + <div class="formz_items"><div class="formz_field"><label for="a">A: </label><input type="number" name="a" id="a" required value="x"></div><div class="formz_field"><label for="b">B: </label><span id="b_help_text" class="formz_help_text">this is field b</span><input type="number" name="b" id="b" required value="x" aria-describedby="b_help_text b_error"><span id="b_error" class="formz_error">must be a whole number</span></div><div class="formz_field"><label for="c">C: </label><input type="number" name="c" id="c" value="x"></div></div>
+2 -2
formz_string/birdie_snapshots/three_field_form_with_help_text.accepted
··· 1 1 --- 2 - version: 1.2.3 2 + version: 1.2.4 3 3 title: three field form with help text 4 4 file: ./test/formz_string/simple_test.gleam 5 5 test_name: three_field_form_with_help_test 6 6 --- 7 - <div class="formz_items"><div class="formz_field"><label for="a">A: </label><input type="number" name="a" id="a" required></div><div class="formz_field"><label for="b">B: </label><input type="number" name="b" id="b" required aria-describedby="b_help_text"><span id="b_help_text" class="formz_help_text">this is field b</span></div><div class="formz_field"><label for="c">C: </label><input type="number" name="c" id="c"></div></div> 7 + <div class="formz_items"><div class="formz_field"><label for="a">A: </label><input type="number" name="a" id="a" required></div><div class="formz_field"><label for="b">B: </label><span id="b_help_text" class="formz_help_text">this is field b</span><input type="number" name="b" id="b" required aria-describedby="b_help_text"></div><div class="formz_field"><label for="c">C: </label><input type="number" name="c" id="c"></div></div>
+4 -4
formz_string/manifest.toml
··· 7 7 { name = "edit_distance", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "edit_distance", source = "hex", outer_checksum = "A1E485C69A70210223E46E63985FA1008B8B2DDA9848B7897469171B29020C05" }, 8 8 { name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" }, 9 9 { name = "formz", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "justin"], source = "local", path = "../formz" }, 10 - { name = "glance", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "1510D4A03C28880E62974389E5BF1A5A185036BA07392F1D769620706A9E042F" }, 10 + { name = "glance", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "E155BA1A787FD11827048355021C0390D2FE9A518485526F631A9D472858CC6D" }, 11 11 { name = "gleam_community_ansi", version = "1.4.1", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "4CD513FC62523053E62ED7BAC2F36136EC17D6A8942728250A9A00A15E340E4B" }, 12 12 { name = "gleam_community_colour", version = "1.4.1", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "386CB9B01B33371538672EEA8A6375A0A0ADEF41F17C86DDCB81C92AD00DA610" }, 13 - { name = "gleam_erlang", version = "0.32.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "B18643083A0117AC5CFD0C1AEEBE5469071895ECFA426DCC26517A07F6AD9948" }, 13 + { name = "gleam_erlang", version = "0.33.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "A1D26B80F01901B59AABEE3475DD4C18D27D58FA5C897D922FCB9B099749C064" }, 14 14 { name = "gleam_json", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "0A57FB5666E695FD2BEE74C0428A98B0FC11A395D2C7B4CDF5E22C5DD32C74C6" }, 15 - { name = "gleam_stdlib", version = "0.45.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "206FCE1A76974AECFC55AEBCD0217D59EDE4E408C016E2CFCCC8FF51278F186E" }, 15 + { name = "gleam_stdlib", version = "0.47.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "3B22D46743C46498C8355365243327AC731ECD3959216344FA9CF9AD348620AC" }, 16 16 { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, 17 - { name = "glexer", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "BD477AD657C2B637FEF75F2405FAEFFA533F277A74EF1A5E17B55B1178C228FB" }, 17 + { name = "glexer", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "25E87F25706749E40C3CDC72D2E52AEA12260B23D14FD9E09A1B524EF393485E" }, 18 18 { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, 19 19 { name = "rank", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "rank", source = "hex", outer_checksum = "5660E361F0E49CBB714CC57CC4C89C63415D8986F05B2DA0C719D5642FAD91C9" }, 20 20 { name = "simplifile", version = "2.2.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0DFABEF7DC7A9E2FF4BB27B108034E60C81BEBFCB7AB816B9E7E18ED4503ACD8" },
+133 -147
formz_string/src/formz_string/simple.gleam
··· 1 1 import formz 2 2 import formz_string/widget 3 + import gleam/int 3 4 import gleam/list 4 5 import gleam/string 5 6 6 7 pub fn generate(form) -> String { 7 - "<div class=\"formz_items\">" 8 - <> { 9 - form 10 - |> formz.items 11 - |> list.map(generate_item) 12 - |> string.join("") 13 - } 14 - <> "</div>" 8 + formz.items(form) 9 + |> list.map(generate_item) 10 + |> string.join("") 11 + |> wrap("<div class=\"formz_items\">", _, "</div>") 15 12 } 16 13 17 14 pub fn generate_item(item: formz.Item(widget.Widget)) -> String { 18 15 case item { 19 - formz.Field(field, state, widget.Hidden) -> 20 - "<input" 21 - <> { " type=\"hidden\"" } 22 - <> { " name=\"" <> field.name <> "\"" } 23 - <> { " value=\"" <> state.value <> "\"" } 24 - <> ">" 25 - formz.ListField(field, states, _, widget.Hidden) -> 26 - states 27 - |> list.map(fn(state) { 28 - "<input" 29 - <> { " type=\"hidden\"" } 30 - <> { " name=\"" <> field.name <> "[]\"" } 31 - <> { " value=\"" <> state.value <> "\"" } 32 - <> ">" 33 - }) 34 - |> string.join("") 35 - formz.Field(field, state, widget.Widget(make_widget)) -> { 36 - let id = field.name 16 + formz.Field(config, state, widget.Hidden) -> hidden_input(config, state) 37 17 38 - let label_el = 39 - "<label for=\"" <> id <> "\">" <> field.label <> ": </label>" 18 + formz.ListField(config, states, _, widget.Hidden) -> 19 + states |> list.map(hidden_input(config, _)) |> string.join("") 40 20 41 - let #(help_text_el, help_text_id) = case field.help_text { 42 - "" -> #("", "") 43 - _ -> { 44 - #( 45 - "<span" 46 - <> { " id=\"" <> id <> "_help_text" <> "\"" } 47 - <> { " class=\"formz_help_text\"" } 48 - <> ">" 49 - <> field.help_text 50 - <> "</span>", 51 - id <> "_help_text", 52 - ) 53 - } 54 - } 21 + formz.Field(config, state, widget.Widget(make_widget)) -> { 22 + let id = config.name 55 23 56 - let #(errors_el, errors_id) = case state { 57 - formz.Invalid(error:, ..) -> { 58 - #( 59 - "<span" 60 - <> { " id=\"" <> id <> "_error" <> "\"" } 61 - <> { " class=\"formz_error\"" } 62 - <> ">" 63 - <> error 64 - <> "</span>", 65 - id <> "_error", 66 - ) 67 - } 68 - _ -> #("", "") 69 - } 24 + let label = label(id, config.label) 25 + let help_text = help_text(id, config.help_text, "span", "formz_help_text") 26 + let error = error(id, state, "span", "formz_error") 70 27 71 - let widget_el = 72 - make_widget( 73 - field, 74 - state, 75 - widget.Args( 76 - id, 77 - widget.LabelledByLabelFor, 78 - widget.DescribedByElementsWithIds([help_text_id, errors_id]), 79 - ), 28 + let args = 29 + widget.Args( 30 + id, 31 + widget.LabelledByLabelFor, 32 + widget.DescribedByElementsWithIds([help_text.id, error.id]), 80 33 ) 34 + let widget_el = make_widget(config, state, args) 81 35 82 36 "<div class=\"formz_field\">" 83 - <> label_el 37 + <> label.element 38 + <> help_text.element 84 39 <> widget_el 85 - <> help_text_el 86 - <> errors_el 40 + <> error.element 87 41 <> "</div>" 88 42 } 89 - formz.ListField(field, states, _, widget.Widget(make_widget)) -> { 90 - let id = field.name 91 43 92 - let #(legend_el, legend_id) = #( 93 - "<legend id=\"" <> id <> "_legend\">" <> field.label <> ": </legend>", 94 - id <> "_legend", 95 - ) 44 + formz.ListField(config, states, _, widget.Widget(make_widget)) -> { 45 + let id = config.name 96 46 97 - let #(help_text_el, help_text_id) = case field.help_text { 98 - "" -> #("", "") 99 - _ -> { 100 - #( 101 - "<span" 102 - <> { " id=\"" <> id <> "_help_text" <> "\"" } 103 - <> { " class=\"formz_help_text\"" } 104 - <> ">" 105 - <> field.help_text 106 - <> "</span>", 107 - id <> "_help_text", 108 - ) 109 - } 110 - } 47 + let legend = legend(id, config.label) 48 + let help_text = help_text(id, config.help_text, "span", "formz_help_text") 111 49 112 50 let widgets_el = 113 51 states 114 - |> list.map(fn(state) { 115 - let #(errors_el, errors_id) = case state { 116 - formz.Invalid(error:, ..) -> { 117 - #( 118 - "<span" 119 - <> { " id=\"" <> id <> "_error" <> "\"" } 120 - <> { " class=\"formz_error\"" } 121 - <> ">" 122 - <> error 123 - <> "</span>", 124 - id <> "_error", 125 - ) 126 - } 127 - _ -> #("", "") 128 - } 52 + |> list.index_map(fn(state, i) { 53 + let id = id <> "_" <> int.to_string(i) 54 + let error = error(id, state, "span", "formz_error") 129 55 130 - let widget_el = 131 - make_widget( 132 - field, 133 - state, 134 - widget.Args( 135 - id, 136 - widget.LabelledByElementsWithIds([legend_id]), 137 - widget.DescribedByElementsWithIds([help_text_id, errors_id]), 138 - ), 56 + let args = 57 + widget.Args( 58 + id, 59 + widget.LabelledByElementsWithIds([legend.id]), 60 + widget.DescribedByElementsWithIds([help_text.id, error.id]), 139 61 ) 140 62 141 - widget_el <> errors_el 63 + make_widget(config, state, args) <> error.element 142 64 }) 143 - |> string.join("</li><li>") 144 - let widgets_el = "<ol><li>" <> widgets_el <> "</ol>" 65 + |> list.map(wrap("<li>", _, "</li>")) 66 + |> string.join("") 67 + |> wrap("<ol>", _, "</ol>") 145 68 146 69 "<fieldset class=\"formz_listfield\">" 147 - <> legend_el 148 - <> help_text_el 70 + <> legend.element 71 + <> help_text.element 149 72 <> widgets_el 150 73 <> "</fieldset>" 151 74 } 152 - formz.SubForm(subform, items) -> { 153 - let #(help_text_el, help_text_id) = case subform.help_text { 154 - "" -> #("", "") 155 - _ -> { 156 - #( 157 - "<p" 158 - <> { " id=\"" <> subform.name <> "_help_text" <> "\"" } 159 - <> { " class=\"formz_help_text\"" } 160 - <> ">" 161 - <> subform.help_text 162 - <> "</p>", 163 - subform.name <> "_help_text", 164 - ) 165 - } 166 - } 167 75 168 - let described_by_attr = case help_text_id { 169 - "" -> "" 170 - _ -> { 171 - " aria-describedby=\"" <> help_text_id <> "\"" 172 - } 173 - } 76 + formz.SubForm(config, items) -> { 77 + let id = config.name 174 78 175 - { "<fieldset" <> described_by_attr <> ">" } 176 - <> { "<legend>" <> subform.label <> "</legend>" } 177 - <> help_text_el 178 - <> { "<div>" } 179 - <> { 180 - list.map(items, generate_item) 181 - |> string.join("") 182 - } 183 - <> "</div>" 79 + let legend = legend(id, config.label) 80 + let help_text = help_text(id, config.help_text, "p", "formz_help_text") 81 + 82 + let items_el = 83 + "<div>" <> list.map(items, generate_item) |> string.join("") <> "</div>" 84 + 85 + { "<fieldset" <> described_by_attr(help_text.id) <> ">" } 86 + <> legend.element 87 + <> help_text.element 88 + <> items_el 184 89 <> "</fieldset>" 185 90 } 186 91 } 187 92 } 93 + 94 + fn wrap(start: String, inside: String, end: String) -> String { 95 + start <> inside <> end 96 + } 97 + 98 + fn described_by_attr(id) { 99 + case id { 100 + "" -> "" 101 + _ -> " aria-describedby=\"" <> id <> "\"" 102 + } 103 + } 104 + 105 + pub type ElementAndId { 106 + ElementAndId(element: String, id: String) 107 + } 108 + 109 + pub fn label(id, label) -> ElementAndId { 110 + ElementAndId("<label for=\"" <> id <> "\">" <> label <> ": </label>", "") 111 + } 112 + 113 + pub fn legend(id, label) -> ElementAndId { 114 + ElementAndId( 115 + "<legend id=\"" <> id <> "_legend\">" <> label <> ": </legend>", 116 + id <> "_legend", 117 + ) 118 + } 119 + 120 + fn hidden_input(config: formz.Config, state: formz.InputState) -> String { 121 + let value_attr = case state.value { 122 + "" -> " value" 123 + _ -> " value=\"" <> widget.sanitize_attr(state.value) <> "\"" 124 + } 125 + "<input" 126 + <> { " type=\"hidden\"" } 127 + <> { " name=\"" <> config.name <> "\"" } 128 + <> value_attr 129 + <> ">" 130 + } 131 + 132 + pub fn help_text( 133 + id: String, 134 + help_text: String, 135 + element_name: String, 136 + class_name: String, 137 + ) -> ElementAndId { 138 + case help_text { 139 + "" -> ElementAndId("", "") 140 + _ -> 141 + ElementAndId( 142 + { "<" <> element_name } 143 + <> { " id=\"" <> id <> "_help_text" <> "\"" } 144 + <> { " class=\"" <> class_name <> "\"" } 145 + <> ">" 146 + <> help_text 147 + <> { "</" <> element_name <> ">" }, 148 + id <> "_help_text", 149 + ) 150 + } 151 + } 152 + 153 + pub fn error( 154 + id: String, 155 + state: formz.InputState, 156 + element_name: String, 157 + class_name: String, 158 + ) -> ElementAndId { 159 + case state { 160 + formz.Invalid(error:, ..) -> { 161 + ElementAndId( 162 + { "<" <> element_name } 163 + <> { " id=\"" <> id <> "_error" <> "\"" } 164 + <> { " class=\"" <> class_name <> "\"" } 165 + <> ">" 166 + <> error 167 + <> { "</" <> element_name <> ">" }, 168 + id <> "_error", 169 + ) 170 + } 171 + _ -> ElementAndId("", "") 172 + } 173 + }
+36 -34
formz_string/src/formz_string/widget.gleam
··· 9 9 //// generators, and it's use is optional if you have different needs. 10 10 11 11 import formz 12 - import formz/field 13 12 import gleam/list 14 13 import gleam/string 15 14 16 15 pub type Widget { 17 - Widget(fn(field.Field, formz.InputState, Args) -> String) 16 + Widget(fn(formz.Config, formz.InputState, Args) -> String) 18 17 Hidden 19 18 } 20 19 ··· 34 33 /// pointing to this input's id. This has the best accessibility support 35 34 /// and should be [preferred when possible](https://www.w3.org/WAI/tutorials/forms/labels/). 36 35 LabelledByLabelFor 37 - /// The input should be labelled using the `Field`'s `label` field. 36 + /// The input should be labelled using the `formz.Config`'s `label`. 38 37 LabelledByFieldValue 39 38 /// The input is labelled by elements with the specified ids. 40 39 LabelledByElementsWithIds(ids: List(String)) ··· 47 46 DescribedByNone 48 47 } 49 48 50 - fn sanitize_attr(str: String) -> String { 49 + pub fn sanitize_attr(str: String) -> String { 51 50 str 52 51 |> string.replace("\"", "&quot;") 53 52 |> string.replace(">", "&gt;") ··· 157 156 /// Create an `<input type="checkbox">`. The checkbox is checked 158 157 /// if the value is "on" (the browser default). 159 158 pub fn checkbox_widget() -> Widget { 160 - Widget(fn(field: field.Field, state: formz.InputState, args: Args) { 159 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 161 160 let value = state.value 161 + 162 162 let state = case state { 163 163 formz.Unvalidated(_, requirement) -> formz.Unvalidated("", requirement) 164 164 formz.Valid(_, requirement) -> formz.Valid("", requirement) 165 165 formz.Invalid(_, requirement, e) -> formz.Invalid("", requirement, e) 166 166 } 167 - do_input_widget(field, state, args, "checkbox", [checked_attr(value)]) 167 + do_input_widget(config, state, args, "checkbox", [checked_attr(value)]) 168 168 }) 169 169 } 170 170 ··· 175 175 /// the step size. If you truly need any float, then a `type="text"` input might be a 176 176 /// better choice. 177 177 pub fn number_widget(step_size: String) -> Widget { 178 - Widget(fn(field: field.Field, state: formz.InputState, args: Args) { 179 - do_input_widget(field, state, args, "number", [step_size_attr(step_size)]) 178 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 179 + do_input_widget(config, state, args, "number", [step_size_attr(step_size)]) 180 180 }) 181 181 } 182 182 183 183 /// Create an `<input type="password">`. This will not output the value in the 184 184 /// generated HTML for privacy/security concerns. 185 185 pub fn password_widget() -> Widget { 186 - Widget(fn(field: field.Field, state: formz.InputState, args: Args) { 186 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 187 187 let state = case state { 188 188 formz.Unvalidated(_, requirement) -> formz.Unvalidated("", requirement) 189 189 formz.Valid(_, requirement) -> formz.Valid("", requirement) 190 190 formz.Invalid(_, requirement, e) -> formz.Invalid("", requirement, e) 191 191 } 192 - do_input_widget(field, state, args, "password", []) 192 + do_input_widget(config, state, args, "password", []) 193 193 }) 194 194 } 195 195 196 196 /// Generate any `<input>` like `type="text"`, `type="email"` or 197 197 /// `type="url"`. 198 198 pub fn input_widget(type_: String) -> Widget { 199 - Widget(fn(field: field.Field, state: formz.InputState, args: Args) { 200 - do_input_widget(field, state, args, type_, []) 199 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 200 + do_input_widget(config, state, args, type_, []) 201 201 }) 202 202 } 203 203 204 204 fn do_input_widget( 205 - field: field.Field, 205 + config: formz.Config, 206 206 state: formz.InputState, 207 207 args: Args, 208 208 type_: String, ··· 210 210 ) -> String { 211 211 "<input" 212 212 <> type_attr(type_) 213 - <> name_attr(field.name) 213 + <> name_attr(config.name) 214 214 <> id_attr(args.id) 215 215 <> required_attr(state.requirement) 216 - <> disabled_attr(field.disabled) 216 + <> disabled_attr(config.disabled) 217 217 <> value_attr(state.value) 218 - <> aria_label_attr(args.labelled_by, field.label) 218 + <> aria_label_attr(args.labelled_by, config.label) 219 219 <> aria_describedby_attr(args.described_by) 220 220 <> extra_attrs |> string.join("") 221 221 <> ">" ··· 223 223 224 224 /// Create a `<textarea></textarea>`. 225 225 pub fn textarea_widget() -> Widget { 226 - Widget(fn(field: field.Field, state: formz.InputState, args: Args) -> String { 227 - // https://chriscoyier.net/2023/09/29/css-solves-auto-expanding-textareas-probably-eventually/ 228 - // https://til.simonwillison.net/css/resizing-textarea 229 - "<textarea" 230 - <> name_attr(field.name) 231 - <> id_attr(args.id) 232 - <> required_attr(state.requirement) 233 - <> disabled_attr(field.disabled) 234 - <> aria_label_attr(args.labelled_by, field.label) 235 - <> aria_describedby_attr(args.described_by) 236 - <> ">" 237 - <> state.value 238 - <> "</textarea>" 239 - }) 226 + Widget( 227 + fn(config: formz.Config, state: formz.InputState, args: Args) -> String { 228 + // https://chriscoyier.net/2023/09/29/css-solves-auto-expanding-textareas-probably-eventually/ 229 + // https://til.simonwillison.net/css/resizing-textarea 230 + "<textarea" 231 + <> name_attr(config.name) 232 + <> id_attr(args.id) 233 + <> required_attr(state.requirement) 234 + <> disabled_attr(config.disabled) 235 + <> aria_label_attr(args.labelled_by, config.label) 236 + <> aria_describedby_attr(args.described_by) 237 + <> ">" 238 + <> state.value 239 + <> "</textarea>" 240 + }, 241 + ) 240 242 } 241 243 242 244 /// Create a `<input type="hidden">`. This is useful for if a field is just ··· 250 252 /// of variants is a two-tuple, where the first item is the text to display and 251 253 /// the second item is the value. 252 254 pub fn select_widget(variants: List(#(String, String))) -> Widget { 253 - Widget(fn(field: field.Field, state: formz.InputState, args: Args) { 255 + Widget(fn(config: formz.Config, state: formz.InputState, args: Args) { 254 256 let choices = 255 257 list.map(variants, fn(variant) { 256 258 let val = variant.1 ··· 266 268 267 269 { 268 270 "<select" 269 - <> name_attr(field.name) 271 + <> name_attr(config.name) 270 272 <> id_attr(args.id) 271 273 <> required_attr(state.requirement) 272 - <> disabled_attr(field.disabled) 273 - <> aria_label_attr(args.labelled_by, field.label) 274 + <> disabled_attr(config.disabled) 275 + <> aria_label_attr(args.labelled_by, config.label) 274 276 <> aria_describedby_attr(args.described_by) 275 277 <> ">" 276 278 }
+63 -13
formz_string/test/formz_string/simple_test.gleam
··· 1 1 import birdie 2 2 import formz 3 - import formz/field.{field} 4 - import formz/subform 5 3 import formz_string/definition 6 4 import formz_string/simple 5 + import formz_string/widget 7 6 import gleeunit 8 7 9 8 pub fn main() { ··· 11 10 } 12 11 13 12 pub fn three_field_form() { 14 - use a <- formz.require(field("a"), definition.integer_field()) 15 - use b <- formz.require(field("b"), definition.integer_field()) 16 - use c <- formz.optional(field("c"), definition.integer_field()) 13 + use a <- formz.required_field(formz.named("a"), definition.integer_field()) 14 + use b <- formz.required_field(formz.named("b"), definition.integer_field()) 15 + use c <- formz.field(formz.named("c"), definition.integer_field()) 17 16 18 17 formz.create_form(#(a, b, c)) 19 18 } 20 19 21 20 pub fn one_field_and_subform_form() { 22 - use a <- formz.require(field("a"), definition.integer_field()) 23 - use b <- formz.subform(subform.subform("b"), three_field_form()) 21 + use a <- formz.required_field(formz.named("a"), definition.integer_field()) 22 + use b <- formz.subform(formz.named("b"), three_field_form()) 24 23 25 24 formz.create_form(#(a, b)) 26 25 } 27 26 28 27 pub fn hidden_field_form() { 29 - use a <- formz.require( 30 - field("a"), 28 + use a <- formz.required_field( 29 + formz.named("a"), 31 30 definition.integer_field() |> definition.make_hidden, 32 31 ) 33 32 34 33 formz.create_form(#(a)) 35 34 } 36 35 36 + pub fn list_field_form() { 37 + use a <- formz.limited_list( 38 + formz.limit_between(2, 3), 39 + formz.named("a"), 40 + definition.integer_field(), 41 + ) 42 + formz.create_form(a) 43 + } 44 + 37 45 pub fn three_field_form_test() { 38 46 three_field_form() 39 47 |> simple.generate ··· 49 57 50 58 pub fn three_field_form_with_help_test() { 51 59 three_field_form() 52 - |> formz.update_field("b", field.set_help_text(_, "this is field b")) 60 + |> formz.update_config("b", formz.set_help_text(_, "this is field b")) 53 61 |> simple.generate 54 62 |> birdie.snap(title: "three field form with help text") 55 63 } ··· 66 74 three_field_form() 67 75 |> formz.data([#("a", "x"), #("b", "x"), #("c", "x")]) 68 76 |> formz.validate(["b"]) 69 - |> formz.update_field("b", field.set_help_text(_, "this is field b")) 77 + |> formz.update_config("b", formz.set_help_text(_, "this is field b")) 70 78 |> simple.generate 71 79 |> birdie.snap(title: "three field form with error and help text") 72 80 } 73 81 74 82 pub fn three_field_form_with_disabled_test() { 75 83 three_field_form() 76 - |> formz.update_field("b", field.set_disabled(_, True)) 84 + |> formz.update_config("b", formz.set_disabled(_, True)) 77 85 |> simple.generate 78 86 |> birdie.snap(title: "three field form with disabled field") 79 87 } ··· 86 94 87 95 pub fn subform_with_help_text_test() { 88 96 one_field_and_subform_form() 89 - |> formz.update_subform("b", subform.set_help_text(_, "this is subform b")) 97 + |> formz.update_config("b", formz.set_help_text(_, "this is subform b")) 90 98 |> simple.generate 91 99 |> birdie.snap(title: "subform with help text") 92 100 } 93 101 102 + pub fn subform_with_help_text_and_error_test() { 103 + one_field_and_subform_form() 104 + |> formz.update_config("b", formz.set_help_text(_, "this is subform b")) 105 + |> formz.field_error("b.a", "woops") 106 + |> simple.generate 107 + |> birdie.snap(title: "subform with help text and error") 108 + } 109 + 94 110 pub fn hidden_field_form_test() { 95 111 hidden_field_form() 112 + |> formz.data([#("a", "1")]) 96 113 |> simple.generate 97 114 |> birdie.snap(title: "hidden field form") 98 115 } 116 + 117 + pub fn hidden_field_form_no_value_test() { 118 + hidden_field_form() 119 + |> simple.generate 120 + |> birdie.snap(title: "hidden field form no value") 121 + } 122 + 123 + pub fn list_field_test() { 124 + list_field_form() 125 + |> simple.generate 126 + |> birdie.snap(title: "list field form") 127 + } 128 + 129 + pub fn list_field_with_error_test() { 130 + list_field_form() 131 + |> formz.validate_all 132 + |> formz.listfield_errors("a", [Ok(Nil), Error("woops")]) 133 + |> simple.generate 134 + |> birdie.snap(title: "list field form with error") 135 + } 136 + 137 + pub fn list_field_hidden_test() { 138 + list_field_form() 139 + |> formz.update("a", fn(item) { 140 + case item { 141 + formz.ListField(..) -> formz.ListField(..item, widget: widget.Hidden) 142 + _ -> item 143 + } 144 + }) 145 + |> formz.data([#("a", "1"), #("a", "2")]) 146 + |> simple.generate 147 + |> birdie.snap(title: "list field form hidden") 148 + }
+17 -18
formz_string/test/formz_string/widgets_test.gleam
··· 1 1 import birdie 2 2 import formz 3 - import formz/field 4 3 import formz_string/widget 5 4 import gleeunit 6 5 ··· 10 9 11 10 fn get_make_fun( 12 11 widget: widget.Widget, 13 - ) -> fn(field.Field, formz.InputState, widget.Args) -> String { 12 + ) -> fn(formz.Config, formz.InputState, widget.Args) -> String { 14 13 let assert widget.Widget(fun) = widget 15 14 fun 16 15 } 17 16 18 17 pub fn input_labelled_by_field_value_test() { 19 18 get_make_fun(widget.input_widget("text"))( 20 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 19 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 21 20 formz.Valid("hello", formz.Optional), 22 21 widget.Args("", widget.LabelledByFieldValue, widget.DescribedByNone), 23 22 ) ··· 26 25 27 26 pub fn input_labelled_by_element_with_id_test() { 28 27 get_make_fun(widget.input_widget("text"))( 29 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 28 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 30 29 formz.Valid("hello", formz.Optional), 31 30 widget.Args( 32 31 "", ··· 39 38 40 39 pub fn input_labelled_by_label_for_test() { 41 40 get_make_fun(widget.input_widget("text"))( 42 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 41 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 43 42 formz.Valid("hello", formz.Optional), 44 43 widget.Args("", widget.LabelledByLabelFor, widget.DescribedByNone), 45 44 ) ··· 48 47 49 48 pub fn input_described_by_elements_with_ids_test() { 50 49 get_make_fun(widget.input_widget("text"))( 51 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 50 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 52 51 formz.Valid("hello", formz.Optional), 53 52 widget.Args( 54 53 "", ··· 61 60 62 61 pub fn input_described_by_elements_with_ids_all_empty_test() { 63 62 get_make_fun(widget.input_widget("text"))( 64 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 63 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 65 64 formz.Valid("hello", formz.Optional), 66 65 widget.Args( 67 66 "", ··· 74 73 75 74 pub fn input_required_test() { 76 75 get_make_fun(widget.input_widget("text"))( 77 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 76 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 78 77 formz.Valid("hello", formz.Required), 79 78 widget.Args("", widget.LabelledByLabelFor, widget.DescribedByNone), 80 79 ) ··· 83 82 84 83 pub fn input_disabled_test() { 85 84 get_make_fun(widget.input_widget("text"))( 86 - field.Field(name: "name", label: "Label", help_text: "", disabled: True), 85 + formz.Config(name: "name", label: "Label", help_text: "", disabled: True), 87 86 formz.Valid("hello", formz.Optional), 88 87 widget.Args("", widget.LabelledByLabelFor, widget.DescribedByNone), 89 88 ) ··· 92 91 93 92 pub fn input_sanitized_value_test() { 94 93 get_make_fun(widget.input_widget("text"))( 95 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 94 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 96 95 formz.Valid("hello\"<-_=>", formz.Optional), 97 96 widget.Args("", widget.LabelledByLabelFor, widget.DescribedByNone), 98 97 ) ··· 101 100 102 101 pub fn checkbox_checked_test() { 103 102 get_make_fun(widget.checkbox_widget())( 104 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 103 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 105 104 formz.Valid("on", formz.Optional), 106 105 widget.Args("", widget.LabelledByLabelFor, widget.DescribedByNone), 107 106 ) ··· 110 109 111 110 pub fn checkbox_unchecked_test() { 112 111 get_make_fun(widget.checkbox_widget())( 113 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 112 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 114 113 formz.Valid("", formz.Optional), 115 114 widget.Args("", widget.LabelledByLabelFor, widget.DescribedByNone), 116 115 ) ··· 119 118 120 119 pub fn password_test() { 121 120 get_make_fun(widget.password_widget())( 122 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 121 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 123 122 formz.Valid("pass", formz.Optional), 124 123 widget.Args("", widget.LabelledByLabelFor, widget.DescribedByNone), 125 124 ) ··· 128 127 129 128 pub fn numeric_no_step_test() { 130 129 get_make_fun(widget.number_widget(""))( 131 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 130 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 132 131 formz.Valid("1", formz.Optional), 133 132 widget.Args("", widget.LabelledByLabelFor, widget.DescribedByNone), 134 133 ) ··· 137 136 138 137 pub fn numeric_step_test() { 139 138 get_make_fun(widget.number_widget("0.1"))( 140 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 139 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 141 140 formz.Valid("1.0", formz.Optional), 142 141 widget.Args("", widget.LabelledByLabelFor, widget.DescribedByNone), 143 142 ) ··· 148 147 get_make_fun( 149 148 widget.select_widget([#("One", "0"), #("Two", "1"), #("Three", "2")]), 150 149 )( 151 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 150 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 152 151 formz.Valid("", formz.Optional), 153 152 widget.Args("", widget.LabelledByLabelFor, widget.DescribedByNone), 154 153 ) ··· 159 158 get_make_fun( 160 159 widget.select_widget([#("One", "0"), #("Two", "1"), #("Three", "2")]), 161 160 )( 162 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 161 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 163 162 formz.Valid("1", formz.Optional), 164 163 widget.Args("", widget.LabelledByLabelFor, widget.DescribedByNone), 165 164 ) ··· 170 169 get_make_fun( 171 170 widget.select_widget([#("One", "0"), #("Two", "1"), #("Three", "2")]), 172 171 )( 173 - field.Field(name: "name", label: "Label", help_text: "", disabled: False), 172 + formz.Config(name: "name", label: "Label", help_text: "", disabled: False), 174 173 formz.Valid("", formz.Required), 175 174 widget.Args("", widget.LabelledByLabelFor, widget.DescribedByNone), 176 175 )