this repo has no description
4
fork

Configure Feed

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

Add a README, plus a bunch of small changes.

+655 -229
+222 -9
formz/README.md
··· 3 3 [![Package Version](https://img.shields.io/hexpm/v/formz)](https://hex.pm/packages/formz) 4 4 [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/formz/) 5 5 6 + A Gleam library for parsing and generating accessible HTML forms. 7 + 8 + > **Note:** This library currently has two non-interoperable ways to define forms, 9 + one using the builder pattern, and one using a series of `use` calls like with 10 + the [toy](https://hexdocs.pm/toy/) or [decode/zero](https://hexdocs.pm/decode/) 11 + packages. After gathering some feedback, only one of them will be kept. 12 + 13 + 14 + HTML forms rendered in the browser and the data they are parsed into are 15 + intrinsically linked. Treating the markup and the parsing as two separate 16 + problems to solve is inconvenient and leads to bugs. This library aims 17 + to make that link explicit and easy to manage, while making it really easy 18 + to make accessible forms. 19 + 6 20 ```sh 7 - gleam add formz@1 21 + gleam add formz@0.1 22 + ``` 23 + 24 + ## Creating a form 25 + 26 + A `formz` form is a list of fields and a decoder function. 27 + 28 + ### builder pattern 29 + 30 + With the builder pattern, you add the fields and then explicitly specify the 31 + decoder function... 32 + 33 + ```gleam 34 + import formz/field.{field} 35 + import formz/formz_builder as formz 36 + import formz_string/definitions 37 + 38 + pub fn make_form() { 39 + formz.new() 40 + |> formz.add(field("username"), definitions.text_field()) 41 + |> formz.add(field("password"), definitions.password_field()) 42 + |> formz.decodes(fn(username) { fn(password) { #(username, password) } }) 43 + } 44 + ``` 45 + 46 + ### `use`/callbacks pattern 47 + 48 + With the `use`/callbakcks pattern, you create the decoder function as you add 49 + the fields... 50 + 51 + ```gleam 52 + import formz/field.{field} 53 + import formz/formz_use as formz 54 + import formz_string/definitions 55 + 56 + pub fn make_form() { 57 + use username <- formz.with(field("username"), definitions.text_field()) 58 + use password <- formz.with(field("password"), definitions.password_field()) 59 + 60 + formz.create_form(#(username, password)) 61 + } 62 + ``` 63 + 64 + ## Creating fields 65 + 66 + There are two parts to adding a field to a form (seen above): 67 + 68 + 1. Specific, unique details about the field, such as its name, label, help text, 69 + disabled/enabled state, etc. 70 + 2. A field "definition" which says (A) how to generate the HTML "widget" 71 + for the field, and (B) how to parse, or "transform" the data from the field. These 72 + definitions are reusable and can be shared between fields, forms and projects. 73 + 74 + ### Field details 75 + 76 + ```gleam 77 + // name is required, the other details are optional 78 + field(named: "username") 79 + |> field.set_label("Username") 80 + |> field.set_help_text("Only alphanumeric characters are allowed.") 81 + ``` 82 + 83 + ```gleam 84 + field(named: "userid") |> field.make_hidden |> field.set_value("42") 85 + ``` 86 + 87 + ### Field definition 88 + 89 + This library is format-agnostic and you can generate HTML widgets as raw 90 + strings, Lustre elements, Nakai nodes, something else, etc. There are 91 + currently three formz libraries that provide common field definitions in 92 + different formats. 93 + 94 + - [formz_string](https://hexdocs.pm/formz_string/) 95 + - [formz_nakai](https://hexdocs.pm/formz_nakai/) 96 + - [formz_lustre](https://hexdocs.pm/formz_lustre/) (untested in a browser, 97 + would it be useful there??) 98 + 99 + 100 + There is also a *simple* validation module with some examples, and to cover 101 + some basics. 102 + 103 + ```gleam 104 + /// you won't often need to do this directly (I think??). The idea is that 105 + /// there'd be libs with the definitions you need. 106 + 107 + import formz/definition.{Definition} 108 + import formz/field 109 + import formz/validation 110 + import formz/widget 111 + import lustre/attribute 112 + import lustre/element 113 + import lustre/element/html 114 + 115 + fn password_widget( 116 + field: field.Field, 117 + args: widget.Args, 118 + ) -> element.Element(msg) { 119 + html.input([ 120 + attribute.type_("password"), 121 + attribute.name(field.name), 122 + attribute.id(args.id), 123 + attribute.attribute("aria-labelledby", field.label), 124 + ]) 125 + } 126 + 127 + pub fn password_field() { 128 + Definition(password_widget, validation.string, "") 129 + } 8 130 ``` 131 + 132 + 133 + 134 + ## Generating HTML for a form 135 + 136 + Generally speaking, the idea with a `formz` form is that you are not going 137 + to generate the HTML for each field individually, but rather, you'd use 138 + a function to loop through each field, generating semantic, accessible 139 + markup for each one. 140 + 141 + The specifics of how you would do this are going 142 + to vary greatly for each project and its styling/markup needs. 143 + 144 + 145 + However, the three `formz_*` libraries mentioned above all provide a 146 + simple form generator function that you can use as is, or as a starting 147 + point for your own. `formz` is BYOS, Bring Your Own Stylesheet, so the 148 + built-in form generators come unstyled. If there is interest, I could add 149 + a super simple CSS file to get the ball rolling and make the default 150 + forms easier to use out of the box. 151 + 152 + That said, you can create the form HTML yourself, directly for each field. 153 + There's an example in the demo project showing how to do this. 154 + 155 + ### Generating form HTML using the `formz_string` library 156 + 157 + The built-in form generators all leave it as homework to add the form tags 158 + and submit buttons. 159 + 9 160 ```gleam 10 - import formz 161 + import formz_string/simple 162 + 163 + pub fn show_form(form) -> String { 164 + "<form method=\"post\">" 165 + <> simple.generate_form(form) 166 + <> "<p><button type\"submit\">Submit</button></p>" 167 + <> "</form>" 168 + } 169 + ``` 170 + 171 + 172 + ## Parsing form data 173 + 174 + You can parse a `formz` form with a tuple of values and names, typically from 175 + a POST request. Here we parse in a `wisp` handler: 176 + 177 + ```gleam 178 + pub fn handle_form_submission(req: Request) -> Response { 179 + use formdata <- wisp.require_form(req) 180 + 181 + let result = make_form() 182 + |> formz.data(formdata.values) 183 + |> formz.parse 11 184 12 - pub fn main() { 13 - // TODO: An example of the project in use 185 + case result { 186 + Ok(credentials) -> { 187 + let #(username, password) = credentials 188 + wisp.ok() 189 + |> wisp.html_body(string_builder.from_string("Hello "<>username<>"!")) 190 + } 191 + Error(form_with_errors) -> { 192 + show_form(form_with_errors) 193 + } 194 + } 14 195 } 15 196 ``` 16 197 17 - Further documentation can be found at <https://hexdocs.pm/formz>. 198 + However, often you want to parse a form, and then... you know... act on that 199 + data, and in doing so you might discover more errors for the form. In this 200 + situation you can use `parse_then_try`: 18 201 19 - ## Development 202 + ```gleam 203 + pub fn handle_form_submission(req: Request) -> Response { 204 + use formdata <- wisp.require_form(req) 205 + 206 + let result = make_form() 207 + |> formz.data(formdata.values) 208 + |> formz.parse_then_try(fn(form, credentials) { 209 + case credentials { 210 + #("admin" as username, "l33t") -> Ok(username) 211 + #("admin", _) -> 212 + form 213 + |> formz.update_field("password", field.set_error(_, "Wrong password")) 214 + |> Error 215 + _ -> 216 + form 217 + |> formz.update_field("username", field.set_error(_, "Wrong username")) 218 + |> Error 219 + } 220 + }) 20 221 21 - ```sh 22 - gleam run # Run the project 23 - gleam test # Run the tests 222 + case result { 223 + Ok(username) -> { 224 + wisp.ok() 225 + |> wisp.html_body(string_builder.from_string("Hello " <> username <> "!")) 226 + } 227 + Error(form_with_errors) -> { 228 + show_form(form_with_errors) 229 + } 230 + } 231 + } 24 232 ``` 233 + 234 + ## See it in action 235 + 236 + There is a demo wisp app showing a few interactive examples of how `formz` works 237 + [in the repo]().
+1 -1
formz/gleam.toml
··· 1 1 name = "formz" 2 - version = "1.0.0" 2 + version = "0.1.0" 3 3 4 4 # Fill out these fields if you intend to generate HTML documentation or publish 5 5 # your project to the Hex package manager.
+2
formz/src/formz.gleam
··· 1 + //// This will eventually be the home of `formz_builder` or `formz_use`. 2 + 1 3 import gleam/io 2 4 3 5 pub fn main() {
+29
formz/src/formz/form_details.gleam
··· 1 + import justin 2 + 3 + pub type FormDetails { 4 + FormDetails(name: String, label: String, help_text: String, disabled: Bool) 5 + } 6 + 7 + pub fn form_details(name) { 8 + FormDetails(name, justin.sentence_case(name), "", False) 9 + } 10 + 11 + pub fn set_name(sub: FormDetails, name: String) -> FormDetails { 12 + FormDetails(..sub, name:) 13 + } 14 + 15 + pub fn set_label(sub: FormDetails, label: String) -> FormDetails { 16 + FormDetails(..sub, label:) 17 + } 18 + 19 + pub fn set_help_text(sub: FormDetails, help_text: String) -> FormDetails { 20 + FormDetails(..sub, help_text:) 21 + } 22 + 23 + pub fn make_disabled(sub: FormDetails) -> FormDetails { 24 + FormDetails(..sub, disabled: True) 25 + } 26 + 27 + pub fn make_enabled(sub: FormDetails) -> FormDetails { 28 + FormDetails(..sub, disabled: False) 29 + }
+15 -15
formz/src/formz/formz_builder.gleam
··· 1 1 import formz/definition.{type Definition} 2 2 import formz/field 3 - import formz/subform 3 + import formz/form_details 4 4 import formz/widget 5 5 6 6 import gleam/dict ··· 23 23 24 24 pub type FormItem(format) { 25 25 Element(field.Field, widget: widget.Widget(format)) 26 - Set(subform.SubForm, items: List(FormItem(format))) 26 + Set(form_details.FormDetails, items: List(FormItem(format))) 27 27 } 28 28 29 29 pub fn new() -> Form(format, a, a, NoDecoder) { ··· 90 90 form_output, 91 91 has_decoder, 92 92 ), 93 - subform: subform.SubForm, 93 + details: form_details.FormDetails, 94 94 sub: Form(format, sub_output, sub_decoder, HasDecoder), 95 95 ) -> Form(format, decoder_step_output, form_output, has_decoder) { 96 96 let sub_items = 97 97 sub.items 98 98 |> map_fields(fn(field) { 99 - field |> field.set_name(subform.name <> "." <> field.name) 99 + field |> field.set_name(details.name <> "." <> field.name) 100 100 }) 101 101 102 - let updated_items = [Set(subform, sub_items), ..previous_form.items] 102 + let updated_items = [Set(details, sub_items), ..previous_form.items] 103 103 104 104 let parse_with = fn(items, decoder: form_output) { 105 105 // can do let assert because we know there's at least one field since 106 106 // we just added one 107 - let assert Ok(#(Set(subform, sub_items), rest)) = pop_element(items) 107 + let assert Ok(#(Set(details, sub_items), rest)) = pop_element(items) 108 108 109 109 let assert Form(_, sub_parse_with, Some(sub_decoder)) = sub 110 110 let form_output = sub_parse_with(sub_items, sub_decoder) ··· 116 116 117 117 // form has errors, but this sub form was good, so add it to the list 118 118 // of items as is. 119 - Error(items), Ok(_value) -> Error([Set(subform, items), ..items]) 119 + Error(items), Ok(_value) -> Error([Set(details, items), ..items]) 120 120 121 121 // form was good so far, but this sub form errored, so need to 122 122 // hop on error track 123 - Ok(_), Error(error_fields) -> Error([Set(subform, error_fields), ..rest]) 123 + Ok(_), Error(error_fields) -> Error([Set(details, error_fields), ..rest]) 124 124 125 125 // form already has errors and this form errored, so add this field 126 126 // to the list of errors 127 127 Error(fields), Error(error_fields) -> 128 - Error(list.prepend(fields, Set(subform, error_fields))) 128 + Error(list.prepend(fields, Set(details, error_fields))) 129 129 } 130 130 } 131 131 Form(items: updated_items, parse_with:, decoder: previous_form.decoder) ··· 202 202 } 203 203 } 204 204 205 - pub fn parse_try( 205 + pub fn parse_then_try( 206 206 form: Form(format, output, decoder, HasDecoder), 207 - apply fun: fn(output, Form(format, output, decoder, HasDecoder)) -> 207 + apply fun: fn(Form(format, output, decoder, HasDecoder), output) -> 208 208 Result(c, Form(format, output, decoder, HasDecoder)), 209 209 ) -> Result(c, Form(format, output, decoder, HasDecoder)) { 210 - parse(form) |> result.try(fun(_, form)) 210 + parse(form) |> result.try(fun(form, _)) 211 211 } 212 212 213 213 pub fn items(form: Form(format, a, b, has_decoder)) -> List(FormItem(format)) { ··· 268 268 }) 269 269 } 270 270 271 - pub fn update_fieldset( 271 + pub fn update_subform( 272 272 form: Form(format, output, decoder, has_decoder), 273 273 name: String, 274 - fun: fn(subform.SubForm) -> subform.SubForm, 274 + fun: fn(form_details.FormDetails) -> form_details.FormDetails, 275 275 ) -> Form(format, output, decoder, has_decoder) { 276 276 update(form, name, fn(item) { 277 277 case item { 278 - Set(subform, items) -> Set(fun(subform), items) 278 + Set(details, items) -> Set(fun(details), items) 279 279 _ -> item 280 280 } 281 281 })
+15 -15
formz/src/formz/formz_use.gleam
··· 1 1 import formz/definition.{type Definition} 2 2 import formz/field 3 - import formz/subform 3 + import formz/form_details 4 4 import formz/widget 5 5 import gleam/dict 6 6 import gleam/list ··· 16 16 17 17 pub type FormItem(format) { 18 18 Element(field.Field, widget: widget.Widget(format)) 19 - Set(subform.SubForm, items: List(FormItem(format))) 19 + Set(form_details.FormDetails, items: List(FormItem(format))) 20 20 } 21 21 22 22 pub fn create_form(thing: thing) -> Form(format, thing) { ··· 87 87 } 88 88 89 89 pub fn with_form( 90 - subform: subform.SubForm, 90 + details: form_details.FormDetails, 91 91 sub: Form(format, sub_output), 92 92 fun: fn(sub_output) -> Form(format, form_output), 93 93 ) -> Form(format, form_output) { ··· 96 96 let sub_items = 97 97 sub.items 98 98 |> map_fields(fn(field) { 99 - field |> field.set_name(subform.name <> "." <> field.name) 99 + field |> field.set_name(details.name <> "." <> field.name) 100 100 }) 101 101 102 - let updated_items = [Set(subform, sub_items), ..next_form.items] 102 + let updated_items = [Set(details, sub_items), ..next_form.items] 103 103 104 104 let parse = fn(items: List(FormItem(format))) { 105 105 // pull out the latest version of this field to get latest input data 106 - let assert [Set(subform, sub_items), ..next_items] = items 106 + let assert [Set(details, sub_items), ..next_items] = items 107 107 108 108 let sub_output = sub.parse(sub_items) 109 109 ··· 117 117 118 118 // form has errors, but this sub form was good, so add it to the list 119 119 // of items as is. 120 - Error(items), Ok(_value) -> Error([Set(subform, items), ..items]) 120 + Error(items), Ok(_value) -> Error([Set(details, items), ..items]) 121 121 122 122 // form was good so far, but this sub form errored, so need to 123 123 // hop on error track 124 124 Ok(_), Error(error_fields) -> 125 - Error([Set(subform, error_fields), ..next_items]) 125 + Error([Set(details, error_fields), ..next_items]) 126 126 127 127 // form already has errors and this form errored, so add this field 128 128 // to the list of errors 129 129 Error(fields), Error(error_fields) -> 130 - Error(list.prepend(fields, Set(subform, error_fields))) 130 + Error(list.prepend(fields, Set(details, error_fields))) 131 131 } 132 132 } 133 133 Form(updated_items, parse: parse, placeholder: next_form.placeholder) ··· 181 181 } 182 182 } 183 183 184 - pub fn parse_try( 184 + pub fn parse_then_try( 185 185 form: Form(format, output), 186 - apply fun: fn(output, Form(format, output)) -> Result(c, Form(format, output)), 186 + apply fun: fn(Form(format, output), output) -> Result(c, Form(format, output)), 187 187 ) -> Result(c, Form(format, output)) { 188 - parse(form) |> result.try(fun(_, form)) 188 + parse(form) |> result.try(fun(form, _)) 189 189 } 190 190 191 191 pub fn items(form: Form(format, output)) -> List(FormItem(format)) { ··· 246 246 }) 247 247 } 248 248 249 - pub fn update_fieldset( 249 + pub fn update_subform( 250 250 form: Form(format, output), 251 251 name: String, 252 - fun: fn(subform.SubForm) -> subform.SubForm, 252 + fun: fn(form_details.FormDetails) -> form_details.FormDetails, 253 253 ) -> Form(format, output) { 254 254 update(form, name, fn(item) { 255 255 case item { 256 - Set(subform, items) -> Set(fun(subform), items) 256 + Set(details, items) -> Set(fun(details), items) 257 257 _ -> item 258 258 } 259 259 })
-25
formz/src/formz/subform.gleam
··· 1 - import justin 2 - 3 - pub type SubForm { 4 - SubForm(name: String, label: String, disabled: Bool) 5 - } 6 - 7 - pub fn subform(name) { 8 - SubForm(name, justin.sentence_case(name), False) 9 - } 10 - 11 - pub fn set_name(fs: SubForm, name: String) -> SubForm { 12 - SubForm(..fs, name:) 13 - } 14 - 15 - pub fn set_label(fs: SubForm, label: String) -> SubForm { 16 - SubForm(..fs, label:) 17 - } 18 - 19 - pub fn make_disabled(fs: SubForm) -> SubForm { 20 - SubForm(..fs, disabled: True) 21 - } 22 - 23 - pub fn make_enabled(fs: SubForm) -> SubForm { 24 - SubForm(..fs, disabled: False) 25 - }
+6 -1
formz/src/formz/widget.gleam
··· 4 4 fn(field.Field, Args) -> format 5 5 6 6 pub type Args { 7 - Args(id: String, labelled_by: LabelledBy) 7 + Args(id: String, labelled_by: LabelledBy, described_by: DescribedBy) 8 8 } 9 9 10 10 pub type LabelledBy { ··· 12 12 LabelledByFieldValue 13 13 LabelledByElementWithId(id: String) 14 14 } 15 + 16 + pub type DescribedBy { 17 + DescribedByElementWithId(id: String) 18 + DescribedByNone 19 + }
+8 -7
formz/test/formz/formz_builder_test.gleam
··· 1 1 import formz/definition 2 2 import formz/field.{field} 3 + import formz/form_details.{form_details} 3 4 import formz/formz_builder.{Element, Set} as formz 4 - import formz/subform.{subform} 5 5 import formz/validation 6 6 import gleam/list 7 7 import gleeunit ··· 108 108 formz.new() 109 109 |> formz.add(field("first"), text_field()) 110 110 |> formz.data([#("first", "world")]) 111 + |> formz.decodes(fn(str) { "one " <> str }) 111 112 |> formz.decodes(fn(str) { "hello " <> str }) 112 113 |> formz.parse 113 114 |> should.equal(Ok("hello world")) ··· 260 261 |> formz.data([#("a", "1"), #("b", "2"), #("c", "3")]) 261 262 262 263 // can succeed 263 - formz.parse_try(f, fn(_, _) { Ok(3) }) 264 + formz.parse_then_try(f, fn(_, _) { Ok(3) }) 264 265 |> should.equal(Ok(3)) 265 266 266 267 // can change type 267 - formz.parse_try(f, fn(_, _) { Ok("it worked") }) 268 + formz.parse_then_try(f, fn(_, _) { Ok("it worked") }) 268 269 |> should.equal(Ok("it worked")) 269 270 270 271 // can error 271 - formz.parse_try(f, fn(_, form) { Error(form) }) 272 + formz.parse_then_try(f, fn(form, _) { Error(form) }) 272 273 |> should.equal(Error(f)) 273 274 274 275 // can change field 275 276 let assert Error(form) = 276 - formz.parse_try(f, fn(_, form) { 277 + formz.parse_then_try(f, fn(form, _) { 277 278 Error(formz.update_field(form, "a", field.set_error(_, "woops"))) 278 279 }) 279 280 let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = ··· 293 294 294 295 let f2 = 295 296 formz.new() 296 - |> formz.add_form(subform("name"), f1) 297 + |> formz.add_form(form_details("name"), f1) 297 298 |> formz.add(field("d"), integer_field()) 298 299 |> formz.decodes(fn(a) { fn(b) { #(a, b) } }) 299 300 ··· 318 319 319 320 let f2 = 320 321 formz.new() 321 - |> formz.add_form(subform("name"), f1) 322 + |> formz.add_form(form_details("name"), f1) 322 323 |> formz.add(field("d"), integer_field()) 323 324 |> formz.decodes(fn(a) { fn(b) { #(a, b) } }) 324 325
+8 -7
formz/test/formz/formz_use_test.gleam
··· 1 1 import formz/definition 2 2 import formz/field.{field} 3 + import formz/form_details.{form_details} 3 4 import formz/formz_use.{Element, Set} as formz 4 - import formz/subform.{subform} 5 + 5 6 import formz/validation 6 7 import gleeunit 7 8 import gleeunit/should ··· 260 261 } 261 262 262 263 let f2 = { 263 - use a <- formz.with_form(subform("name"), f1) 264 + use a <- formz.with_form(form_details("name"), f1) 264 265 use b <- formz.with(field("d"), integer_field()) 265 266 266 267 formz.create_form(#(a, b)) ··· 287 288 } 288 289 289 290 let f2 = { 290 - use a <- formz.with_form(subform("name"), f1) 291 + use a <- formz.with_form(form_details("name"), f1) 291 292 use b <- formz.with(field("d"), integer_field()) 292 293 293 294 formz.create_form(#(a, b)) ··· 320 321 |> formz.data([#("a", "string"), #("b", "2"), #("c", "3.0")]) 321 322 322 323 // can succeed 323 - formz.parse_try(f, fn(_, _) { Ok(3) }) 324 + formz.parse_then_try(f, fn(_, _) { Ok(3) }) 324 325 |> should.equal(Ok(3)) 325 326 326 327 // can change type 327 - formz.parse_try(f, fn(_, _) { Ok("it worked") }) 328 + formz.parse_then_try(f, fn(_, _) { Ok("it worked") }) 328 329 |> should.equal(Ok("it worked")) 329 330 330 331 // can error 331 - formz.parse_try(f, fn(_, form) { Error(form) }) 332 + formz.parse_then_try(f, fn(form, _) { Error(form) }) 332 333 |> should.equal(Error(f)) 333 334 334 335 // can change field 335 336 let assert Error(form) = 336 - formz.parse_try(f, fn(_, form) { 337 + formz.parse_then_try(f, fn(form, _) { 337 338 form 338 339 |> formz.update_field("a", field.set_error(_, "woops")) 339 340 |> Error
+34 -1
formz_demo/priv/static/stylesheet.css
··· 75 75 76 76 .form { 77 77 grid-area: forms; 78 - padding: 0 13px; 78 + padding: 0 15px; 79 79 background: #f3f3f3; 80 80 margin: 0 7px 15px; 81 81 82 + .formz_formitems { 83 + margin: 1em 0; 84 + } 85 + .formz_field { 86 + margin: 1em 0; 87 + 88 + /* label { 89 + display: block; 90 + margin-bottom: 5px; 91 + } 92 + 93 + .formz_widget { 94 + display: block; 95 + margin-bottom: 5px; 96 + 97 + input[type="number"], 98 + input[type="email"], 99 + input[type="password"], 100 + input[type="text"] { 101 + width: 100%; 102 + max-width: 300px; 103 + } 104 + } 105 + 106 + .formz_help_text { 107 + display: block; 108 + } */ 109 + 110 + .formz_error { 111 + color: #bd2901; 112 + /* font-size: 0.9em; */ 113 + } 114 + } 82 115 fieldset { 83 116 margin: 15px 0; 84 117 }
+2 -2
formz_demo/src/formz_demo/example/page.gleam
··· 47 47 } 48 48 49 49 fn get_inputs(form: formz.Form(format, ouput)) { 50 - form |> formz.formitems |> do_get_inputs([]) |> list.reverse 50 + form |> formz.items |> do_get_inputs([]) |> list.reverse 51 51 } 52 52 53 53 fn do_get_inputs(items: List(formz.FormItem(format)), acc) { 54 54 case items { 55 55 [] -> acc 56 - [formz.Item(input, _), ..rest] -> do_get_inputs(rest, [input, ..acc]) 56 + [formz.Element(input, _), ..rest] -> do_get_inputs(rest, [input, ..acc]) 57 57 [formz.Set(_, items), ..rest] -> 58 58 do_get_inputs(list.flatten([items, rest]), acc) 59 59 }
+1 -4
formz_demo/src/formz_demo/examples/all_the_inputs.gleam
··· 12 12 use e <- formz.with(field("e"), definitions.email_field()) 13 13 use f <- formz.with(field("g"), definitions.enum_field(letters())) 14 14 use g <- formz.with(field("h"), definitions.indexed_enum_field(choices)) 15 - use h <- formz.with( 16 - field("i"), 17 - definitions.list_field(["Dog", "Cat", "Bird"]), 18 - ) 15 + use h <- formz.with(field("i"), definitions.list_field(["Dog", "Cat", "Ant"])) 19 16 20 17 formz.create_form(#(a, b, c, d, e, f, g, h)) 21 18 }
+34 -35
formz_demo/src/formz_demo/examples/custom_output.gleam
··· 1 1 import formz/field.{field} 2 2 import formz/formz_use as formz 3 - import formz/subform.{subform} 3 + import formz/widget 4 4 import formz_lustre/definitions 5 - import formz_lustre/simple 6 - import gleam/list 7 5 import lustre/attribute 8 6 import lustre/element/html 9 7 10 8 pub fn make_form() { 11 - use billing_address <- formz.with_form(subform("billing"), address_form()) 12 - use shipping_address <- formz.with_form(subform("shipping"), address_form()) 9 + use username <- formz.with(field("username"), definitions.text_field()) 10 + use password <- formz.with(field("password"), definitions.password_field()) 13 11 14 - formz.create_form(#(billing_address, shipping_address)) 12 + formz.create_form(#(username, password)) 15 13 } 16 14 17 - fn address_form() { 18 - use street <- formz.with(field("street"), definitions.text_field()) 19 - use city <- formz.with(field("city"), definitions.text_field()) 20 - use state <- formz.with(field("state"), definitions.list_field(states_list())) 21 - use postal_code <- formz.with(field("postal_code"), definitions.text_field()) 15 + pub fn format_form(form) { 16 + let assert Ok(formz.Element(username_field, username_widget)) = 17 + formz.get(form, "username") 22 18 23 - formz.create_form(Address(street:, city:, state:, postal_code:)) 24 - } 19 + let assert Ok(formz.Element(password_field, password_widget)) = 20 + formz.get(form, "password") 25 21 26 - pub fn format_form(form) { 27 - let assert Ok(formz.Set(_, billing_inputs)) = formz.get(form, "billing") 28 22 html.div( 29 23 [ 30 24 attribute.role("group"), ··· 32 26 attribute.disabled(True), 33 27 ], 34 28 [ 35 - html.h2([attribute.id("h2")], [html.text("Billing Address")]), 36 - ..list.map(billing_inputs, simple.generate_item) 29 + html.h2([attribute.id("h2")], [html.text("Login Form")]), 30 + html.ul([], [ 31 + html.li([], [ 32 + html.label([attribute.for("username")], [html.text("Username")]), 33 + username_widget( 34 + username_field, 35 + widget.Args( 36 + "username", 37 + widget.LabelledByLabelFor, 38 + widget.DescribedByNone, 39 + ), 40 + ), 41 + ]), 42 + html.li([], [ 43 + html.label([attribute.for("password")], [html.text("Password")]), 44 + password_widget( 45 + password_field, 46 + widget.Args( 47 + "password", 48 + widget.LabelledByLabelFor, 49 + widget.DescribedByNone, 50 + ), 51 + ), 52 + ]), 53 + ]), 37 54 ], 38 55 ) 39 56 } 40 - 41 - pub type Address { 42 - Address(street: String, city: String, state: String, postal_code: String) 43 - } 44 - 45 - fn states_list() { 46 - [ 47 - "Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", 48 - "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", 49 - "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", 50 - "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", 51 - "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", 52 - "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", 53 - "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", 54 - "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", 55 - "Washington", "West Virginia", "Wisconsin", "Wyoming", 56 - ] 57 - }
+4 -15
formz_demo/src/formz_demo/examples/labels.gleam
··· 1 - import formz/definition 2 1 import formz/field.{field} 3 2 import formz/formz_use as formz 4 - import formz/validation 5 - import formz_string/definitions as defs 3 + import formz_string/definitions 6 4 7 5 pub fn make_form() { 8 - use name <- formz.with(field(named: "name"), is: defs.text_field()) 6 + use name <- formz.with(field(named: "name"), is: definitions.text_field()) 9 7 use age <- formz.with( 10 8 field("age") |> field.set_label("Age"), 11 - is: defs.integer_field(), 9 + is: definitions.integer_field(), 12 10 ) 13 11 use height <- formz.with( 14 12 field("height") 15 13 |> field.set_label("Height (cm)") 16 14 |> field.set_help_text("Please enter your height in centimeters"), 17 - is: defs.integer_field(), 18 - ) 19 - 20 - use _something <- formz.with( 21 - field(named: "something") 22 - |> field.make_hidden() 23 - |> field.set_label("Something") 24 - |> field.set_help_text("Please enter something"), 25 - is: defs.text_field() 26 - |> definition.validates(validation.must_be_longer_than(3)), 15 + is: definitions.integer_field(), 27 16 ) 28 17 29 18 formz.create_form(#(name, age, height))
+16 -19
formz_demo/src/formz_demo/examples/login.gleam
··· 1 - import formz/definition 2 1 import formz/field.{field} 3 2 import formz/formz_use as formz 4 3 import formz_string/definitions 5 - import formz_string/widgets 6 4 import wisp 7 5 8 6 pub type Credentials { ··· 15 13 16 14 pub fn make_form() { 17 15 use username <- formz.with(field("username"), definitions.text_field()) 18 - use password <- formz.with( 19 - field("password"), 20 - definitions.text_field() |> definition.set_widget(widgets.password_widget()), 21 - ) 16 + use password <- formz.with(field("password"), definitions.password_field()) 22 17 23 18 formz.create_form(Credentials(username, password)) 24 19 } 25 20 26 21 pub fn handle_post(formdata: wisp.FormData, form) { 27 - use cred, form <- formz.parse_try(form |> formz.data(formdata.values)) 28 - 29 - case cred { 30 - Credentials("admin", "l33t") -> Ok(User(cred.username)) 31 - Credentials("admin", _) -> 32 - form 33 - |> formz.update_field("password", field.set_error(_, "wrong password")) 34 - |> Error 35 - Credentials(_, _) -> 36 - form 37 - |> formz.update_field("username", field.set_error(_, "wrong username")) 38 - |> Error 39 - } 22 + form 23 + |> formz.data(formdata.values) 24 + |> formz.parse_then_try(fn(form, credentials) { 25 + case credentials { 26 + Credentials("admin", "l33t") -> Ok(User(credentials.username)) 27 + Credentials("admin", _) -> 28 + form 29 + |> formz.update_field("password", field.set_error(_, "Wrong password")) 30 + |> Error 31 + Credentials(_, _) -> 32 + form 33 + |> formz.update_field("username", field.set_error(_, "Wrong username")) 34 + |> Error 35 + } 36 + }) 40 37 }
+9 -3
formz_demo/src/formz_demo/examples/sub_form.gleam
··· 1 1 import formz/field.{field} 2 - import formz/fieldset.{fieldset} 2 + import formz/form_details.{form_details} 3 3 import formz/formz_use as formz 4 4 import formz_string/definitions 5 5 6 6 pub fn make_form() { 7 - use billing_address <- formz.with_form(fieldset("billing"), address_form()) 8 - use shipping_address <- formz.with_form(fieldset("shipping"), address_form()) 7 + use billing_address <- formz.with_form( 8 + form_details("billing"), 9 + address_form(), 10 + ) 11 + use shipping_address <- formz.with_form( 12 + form_details("shipping"), 13 + address_form(), 14 + ) 9 15 10 16 formz.create_form(#(billing_address, shipping_address)) 11 17 }
+4
formz_lustre/src/formz_lustre/definitions.gleam
··· 23 23 Definition(widgets.checkbox_widget(), validation.boolean, False) 24 24 } 25 25 26 + pub fn password_field() { 27 + Definition(widgets.password_widget(), validation.string, "") 28 + } 29 + 26 30 pub fn enum_field(variants: List(#(String, enum))) { 27 31 let assert Ok(#(_, first)) = list.first(variants) 28 32 Definition(
+6 -1
formz_lustre/src/formz_lustre/simple.gleam
··· 24 24 attribute.name(f.name), 25 25 attribute.value(f.value), 26 26 ]) 27 + 27 28 formz.Element(f, make_widget) -> { 28 29 let label_el = html.label([], [html.text(f.label), html.text(": ")]) 29 30 ··· 36 37 html.span([attribute.class("widget")], [ 37 38 make_widget( 38 39 f, 39 - widget.Args(id: f.name, labelled_by: widget.LabelledByLabelFor), 40 + widget.Args( 41 + id: f.name, 42 + labelled_by: widget.LabelledByLabelFor, 43 + described_by: widget.DescribedByNone, 44 + ), 40 45 ), 41 46 ]) 42 47
+95 -19
formz_lustre/test/formz_lustre/widgets_test.gleam
··· 65 65 disabled: False, 66 66 required: True, 67 67 value: "", 68 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 68 + args: widget.Args( 69 + "id", 70 + labelled_by: widget.LabelledByLabelFor, 71 + described_by: widget.DescribedByNone, 72 + ), 69 73 ) 70 74 71 75 test_inputs( ··· 78 82 disabled: False, 79 83 required: True, 80 84 value: "", 81 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 85 + args: widget.Args( 86 + "id", 87 + labelled_by: widget.LabelledByLabelFor, 88 + described_by: widget.DescribedByNone, 89 + ), 82 90 ) 83 91 84 92 test_inputs( ··· 91 99 disabled: False, 92 100 required: True, 93 101 value: "val", 94 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 102 + args: widget.Args( 103 + "id", 104 + labelled_by: widget.LabelledByLabelFor, 105 + described_by: widget.DescribedByNone, 106 + ), 95 107 ) 96 108 97 109 test_inputs( ··· 104 116 disabled: False, 105 117 required: True, 106 118 value: "", 107 - args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 119 + args: widget.Args( 120 + "id", 121 + labelled_by: widget.LabelledByFieldValue, 122 + described_by: widget.DescribedByNone, 123 + ), 108 124 ) 109 125 } 110 126 ··· 119 135 disabled: False, 120 136 required: True, 121 137 value: "", 122 - args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 138 + args: widget.Args( 139 + "id", 140 + labelled_by: widget.LabelledByElementWithId("div"), 141 + described_by: widget.DescribedByNone, 142 + ), 123 143 ) 124 144 test_inputs( 125 145 string_widgets.checkbox_widget(), ··· 131 151 disabled: False, 132 152 required: True, 133 153 value: "on", 134 - args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 154 + args: widget.Args( 155 + "id", 156 + labelled_by: widget.LabelledByFieldValue, 157 + described_by: widget.DescribedByNone, 158 + ), 135 159 ) 136 160 137 161 test_inputs( ··· 144 168 disabled: False, 145 169 required: True, 146 170 value: "on", 147 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 171 + args: widget.Args( 172 + "id", 173 + labelled_by: widget.LabelledByLabelFor, 174 + described_by: widget.DescribedByNone, 175 + ), 148 176 ) 149 177 } 150 178 ··· 159 187 disabled: False, 160 188 required: True, 161 189 value: "", 162 - args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 190 + args: widget.Args( 191 + "id", 192 + labelled_by: widget.LabelledByElementWithId("div"), 193 + described_by: widget.DescribedByNone, 194 + ), 163 195 ) 164 196 test_inputs( 165 197 string_widgets.password_widget(), ··· 171 203 disabled: False, 172 204 required: True, 173 205 value: "xxxx", 174 - args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 206 + args: widget.Args( 207 + "id", 208 + labelled_by: widget.LabelledByFieldValue, 209 + described_by: widget.DescribedByNone, 210 + ), 175 211 ) 176 212 177 213 test_inputs( ··· 184 220 disabled: False, 185 221 required: True, 186 222 value: "xxxx", 187 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 223 + args: widget.Args( 224 + "id", 225 + labelled_by: widget.LabelledByLabelFor, 226 + described_by: widget.DescribedByNone, 227 + ), 188 228 ) 189 229 } 190 230 ··· 199 239 disabled: False, 200 240 required: True, 201 241 value: "", 202 - args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 242 + args: widget.Args( 243 + "id", 244 + labelled_by: widget.LabelledByElementWithId("div"), 245 + described_by: widget.DescribedByNone, 246 + ), 203 247 ) 204 248 test_inputs( 205 249 string_widgets.textarea_widget(), ··· 211 255 disabled: False, 212 256 required: True, 213 257 value: "1", 214 - args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 258 + args: widget.Args( 259 + "id", 260 + labelled_by: widget.LabelledByFieldValue, 261 + described_by: widget.DescribedByNone, 262 + ), 215 263 ) 216 264 217 265 test_inputs( ··· 224 272 disabled: False, 225 273 required: True, 226 274 value: "1", 227 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 275 + args: widget.Args( 276 + "id", 277 + labelled_by: widget.LabelledByLabelFor, 278 + described_by: widget.DescribedByNone, 279 + ), 228 280 ) 229 281 } 230 282 ··· 239 291 disabled: False, 240 292 required: True, 241 293 value: "", 242 - args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 294 + args: widget.Args( 295 + "id", 296 + labelled_by: widget.LabelledByElementWithId("div"), 297 + described_by: widget.DescribedByNone, 298 + ), 243 299 ) 244 300 test_inputs( 245 301 string_widgets.hidden_widget(), ··· 251 307 disabled: False, 252 308 required: True, 253 309 value: "1", 254 - args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 310 + args: widget.Args( 311 + "id", 312 + labelled_by: widget.LabelledByFieldValue, 313 + described_by: widget.DescribedByNone, 314 + ), 255 315 ) 256 316 257 317 test_inputs( ··· 264 324 disabled: False, 265 325 required: True, 266 326 value: "1", 267 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 327 + args: widget.Args( 328 + "id", 329 + labelled_by: widget.LabelledByLabelFor, 330 + described_by: widget.DescribedByNone, 331 + ), 268 332 ) 269 333 } 270 334 ··· 280 344 disabled: False, 281 345 required: True, 282 346 value: "", 283 - args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 347 + args: widget.Args( 348 + "id", 349 + labelled_by: widget.LabelledByElementWithId("div"), 350 + described_by: widget.DescribedByNone, 351 + ), 284 352 ) 285 353 test_inputs( 286 354 string_widgets.select_widget(list), ··· 292 360 disabled: False, 293 361 required: True, 294 362 value: "1", 295 - args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 363 + args: widget.Args( 364 + "id", 365 + labelled_by: widget.LabelledByFieldValue, 366 + described_by: widget.DescribedByNone, 367 + ), 296 368 ) 297 369 298 370 test_inputs( ··· 305 377 disabled: False, 306 378 required: True, 307 379 value: "1", 308 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 380 + args: widget.Args( 381 + "id", 382 + labelled_by: widget.LabelledByLabelFor, 383 + described_by: widget.DescribedByNone, 384 + ), 309 385 ) 310 386 }
+4
formz_nakai/src/formz_nakai/definitions.gleam
··· 23 23 Definition(widgets.checkbox_widget(), validation.boolean, False) 24 24 } 25 25 26 + pub fn password_field() { 27 + Definition(widgets.password_widget(), validation.string, "") 28 + } 29 + 26 30 pub fn enum_field(variants: List(#(String, enum))) { 27 31 let assert Ok(#(_, first)) = list.first(variants) 28 32 Definition(
+5 -1
formz_nakai/src/formz_nakai/simple.gleam
··· 29 29 html.span([attr.class("widget")], [ 30 30 make_widget( 31 31 f, 32 - widget.Args(id: f.name, labelled_by: widget.LabelledByLabelFor), 32 + widget.Args( 33 + id: f.name, 34 + labelled_by: widget.LabelledByLabelFor, 35 + described_by: widget.DescribedByNone, 36 + ), 33 37 ), 34 38 ]) 35 39
+95 -19
formz_nakai/test/formz_nakai/widgets_test.gleam
··· 81 81 disabled: False, 82 82 required: True, 83 83 value: "", 84 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 84 + args: widget.Args( 85 + "id", 86 + labelled_by: widget.LabelledByLabelFor, 87 + described_by: widget.DescribedByNone, 88 + ), 85 89 ) 86 90 test_inputs( 87 91 string_widgets.text_like_widget("text"), ··· 93 97 disabled: False, 94 98 required: True, 95 99 value: "", 96 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 100 + args: widget.Args( 101 + "id", 102 + labelled_by: widget.LabelledByLabelFor, 103 + described_by: widget.DescribedByNone, 104 + ), 97 105 ) 98 106 99 107 test_inputs( ··· 106 114 disabled: False, 107 115 required: True, 108 116 value: "val", 109 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 117 + args: widget.Args( 118 + "id", 119 + labelled_by: widget.LabelledByLabelFor, 120 + described_by: widget.DescribedByNone, 121 + ), 110 122 ) 111 123 112 124 test_inputs( ··· 119 131 disabled: False, 120 132 required: True, 121 133 value: "", 122 - args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 134 + args: widget.Args( 135 + "id", 136 + labelled_by: widget.LabelledByFieldValue, 137 + described_by: widget.DescribedByNone, 138 + ), 123 139 ) 124 140 } 125 141 ··· 134 150 disabled: False, 135 151 required: True, 136 152 value: "", 137 - args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 153 + args: widget.Args( 154 + "id", 155 + labelled_by: widget.LabelledByElementWithId("div"), 156 + described_by: widget.DescribedByNone, 157 + ), 138 158 ) 139 159 test_inputs( 140 160 string_widgets.checkbox_widget(), ··· 146 166 disabled: False, 147 167 required: True, 148 168 value: "on", 149 - args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 169 + args: widget.Args( 170 + "id", 171 + labelled_by: widget.LabelledByFieldValue, 172 + described_by: widget.DescribedByNone, 173 + ), 150 174 ) 151 175 152 176 test_inputs( ··· 159 183 disabled: False, 160 184 required: True, 161 185 value: "on", 162 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 186 + args: widget.Args( 187 + "id", 188 + labelled_by: widget.LabelledByLabelFor, 189 + described_by: widget.DescribedByNone, 190 + ), 163 191 ) 164 192 } 165 193 ··· 174 202 disabled: False, 175 203 required: True, 176 204 value: "", 177 - args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 205 + args: widget.Args( 206 + "id", 207 + labelled_by: widget.LabelledByElementWithId("div"), 208 + described_by: widget.DescribedByNone, 209 + ), 178 210 ) 179 211 test_inputs( 180 212 string_widgets.password_widget(), ··· 186 218 disabled: False, 187 219 required: True, 188 220 value: "xxxx", 189 - args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 221 + args: widget.Args( 222 + "id", 223 + labelled_by: widget.LabelledByFieldValue, 224 + described_by: widget.DescribedByNone, 225 + ), 190 226 ) 191 227 192 228 test_inputs( ··· 199 235 disabled: False, 200 236 required: True, 201 237 value: "xxxx", 202 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 238 + args: widget.Args( 239 + "id", 240 + labelled_by: widget.LabelledByLabelFor, 241 + described_by: widget.DescribedByNone, 242 + ), 203 243 ) 204 244 } 205 245 ··· 214 254 disabled: False, 215 255 required: True, 216 256 value: "", 217 - args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 257 + args: widget.Args( 258 + "id", 259 + labelled_by: widget.LabelledByElementWithId("div"), 260 + described_by: widget.DescribedByNone, 261 + ), 218 262 ) 219 263 test_inputs( 220 264 string_widgets.textarea_widget(), ··· 226 270 disabled: False, 227 271 required: True, 228 272 value: "1", 229 - args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 273 + args: widget.Args( 274 + "id", 275 + labelled_by: widget.LabelledByFieldValue, 276 + described_by: widget.DescribedByNone, 277 + ), 230 278 ) 231 279 232 280 test_inputs( ··· 239 287 disabled: False, 240 288 required: True, 241 289 value: "1", 242 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 290 + args: widget.Args( 291 + "id", 292 + labelled_by: widget.LabelledByLabelFor, 293 + described_by: widget.DescribedByNone, 294 + ), 243 295 ) 244 296 } 245 297 ··· 254 306 disabled: False, 255 307 required: True, 256 308 value: "", 257 - args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 309 + args: widget.Args( 310 + "id", 311 + labelled_by: widget.LabelledByElementWithId("div"), 312 + described_by: widget.DescribedByNone, 313 + ), 258 314 ) 259 315 test_inputs( 260 316 string_widgets.hidden_widget(), ··· 266 322 disabled: False, 267 323 required: True, 268 324 value: "1", 269 - args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 325 + args: widget.Args( 326 + "id", 327 + labelled_by: widget.LabelledByFieldValue, 328 + described_by: widget.DescribedByNone, 329 + ), 270 330 ) 271 331 272 332 test_inputs( ··· 279 339 disabled: False, 280 340 required: True, 281 341 value: "1", 282 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 342 + args: widget.Args( 343 + "id", 344 + labelled_by: widget.LabelledByLabelFor, 345 + described_by: widget.DescribedByNone, 346 + ), 283 347 ) 284 348 } 285 349 ··· 295 359 disabled: False, 296 360 required: True, 297 361 value: "", 298 - args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 362 + args: widget.Args( 363 + "id", 364 + labelled_by: widget.LabelledByElementWithId("div"), 365 + described_by: widget.DescribedByNone, 366 + ), 299 367 ) 300 368 test_inputs( 301 369 string_widgets.select_widget(list), ··· 307 375 disabled: False, 308 376 required: True, 309 377 value: "1", 310 - args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 378 + args: widget.Args( 379 + "id", 380 + labelled_by: widget.LabelledByFieldValue, 381 + described_by: widget.DescribedByNone, 382 + ), 311 383 ) 312 384 313 385 test_inputs( ··· 320 392 disabled: False, 321 393 required: True, 322 394 value: "1", 323 - args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 395 + args: widget.Args( 396 + "id", 397 + labelled_by: widget.LabelledByLabelFor, 398 + described_by: widget.DescribedByNone, 399 + ), 324 400 ) 325 401 }
+4
formz_string/src/formz_string/definitions.gleam
··· 23 23 Definition(widgets.checkbox_widget(), validation.boolean, False) 24 24 } 25 25 26 + pub fn password_field() { 27 + Definition(widgets.password_widget(), validation.string, "") 28 + } 29 + 26 30 pub fn enum_field(variants: List(#(String, enum))) { 27 31 let assert Ok(#(_, first)) = list.first(variants) 28 32 Definition(
+36 -30
formz_string/src/formz_string/simple.gleam
··· 5 5 import gleam/string 6 6 7 7 pub fn generate_form(form) -> String { 8 - { 8 + "<div class=\"formz_formitems\">" 9 + <> { 9 10 form 10 11 |> formz.items 11 12 |> list.map(generate_item) 12 13 |> string.join("\n") 13 14 } 15 + <> "</div>" 14 16 } 15 17 16 18 pub fn generate_item(item: formz.FormItem(String)) -> String { ··· 21 23 <> { " name=\"" <> f.name <> "\"" } 22 24 <> { " value\"" <> f.value <> "\"" } 23 25 <> ">" 24 - formz.Element(f, widget) -> 25 - case f.hidden { 26 + formz.Element(f, widget) -> { 27 + let label_el = 28 + "<label for=\"" <> f.name <> "\">" <> f.label <> ": </label>" 29 + let description_el = case string.is_empty(f.help_text) { 26 30 True -> "" 27 - False -> { 28 - let label_el = 29 - "<label for=\"" <> f.name <> "\">" <> f.label <> ": </label>" 30 - let description_el = case string.is_empty(f.help_text) { 31 - True -> "" 32 - False -> "<span class=\"description\">" <> f.help_text <> " </span>" 33 - } 34 - let widget_el = 35 - "<span class=\"widget\">" 36 - <> widget(f, widget.Args(f.name, widget.LabelledByLabelFor)) 37 - <> "</span>" 38 - 39 - let errors_el = case f { 40 - field.Valid(..) -> "<span class=\"error-placeholder\"></span>" 41 - field.Invalid(error:, ..) -> 42 - "<span class=\"errors\">" <> error <> "</span>" 43 - } 31 + False -> 32 + " <span class=\"formz_help_text\">" <> f.help_text <> " </span>" 33 + } 34 + let widget_el = 35 + " <span class=\"formz_widget\">" 36 + <> widget( 37 + f, 38 + widget.Args( 39 + id: f.name, 40 + labelled_by: widget.LabelledByLabelFor, 41 + described_by: widget.DescribedByNone, 42 + ), 43 + ) 44 + <> "</span>" 44 45 45 - "<p class=\"simple_field\">" 46 - <> label_el 47 - <> widget_el 48 - <> description_el 49 - <> errors_el 50 - <> "</p>" 51 - } 46 + let errors_el = case f { 47 + field.Valid(..) -> "" 48 + field.Invalid(error:, ..) -> 49 + " <span class=\"formz_error\">" <> error <> "</span>" 52 50 } 51 + 52 + "<div class=\"formz_field\">" 53 + <> label_el 54 + <> widget_el 55 + <> description_el 56 + <> errors_el 57 + <> "</div>" 58 + } 53 59 formz.Set(s, items) -> { 54 - "<fieldset><legend>" 55 - <> s.label 56 - <> "</legend>" 60 + { "<fieldset><legend>" <> s.label <> "</legend>" } 61 + <> { "<div>" } 57 62 <> { 58 63 list.map(items, generate_item) 59 64 |> string.join("\n") 60 65 } 66 + <> "</div>" 61 67 <> "</fieldset>" 62 68 } 63 69 }