this repo has no description
4
fork

Configure Feed

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

API tweaks and document all recent updates

+352 -282
+275 -204
formz/src/formz.gleam
··· 1 1 //// A form is a list of fields and a decoder function. This module uses a 2 - //// series of callbacks to construct a decoder function as the fields are 2 + //// series of callbacks to construct the decoder function as the fields are 3 3 //// being added to the form. The idea is that you'd have a function that 4 4 //// makes the form using the `use` syntax, and then be able to use the form 5 5 //// later for parsing or rendering in different contexts. ··· 33 33 //// <td> 34 34 //// <a href="#definition">definition</a><br> 35 35 //// <a href="#definition_with_custom_optional">definition_with_custom_optional</a><br> 36 - //// <a href="#transform">transform</a><br> 37 - //// <a href="#transform_with_custom_optional">transform_with_custom_optional</a><br> 38 36 //// <a href="#verify">verify</a><br> 39 37 //// <a href="#widget">widget</a> 40 38 //// </td> ··· 45 43 //// <a href="#limit_at_least">limit_at_least</a><br> 46 44 //// <a href="#limit_at_most">limit_at_most</a><br> 47 45 //// <a href="#limit_between">limit_between</a><br> 48 - //// <a href="#simple_blank_fields">simple_blank_fields</a> 46 + //// <a href="#simple_limit_check">simple_limit_check</a> 49 47 //// </td> 50 48 //// </tr> 51 49 //// <tr> 52 - //// <td>Accessing and manipulating form fields</td> 50 + //// <td>Accessing and manipulating form items</td> 53 51 //// <td> 54 52 //// <a href="#get">get</a><br> 55 53 //// <a href="#items">items</a><br> ··· 67 65 //// 68 66 //// ```gleam 69 67 //// fn make_form() { 70 - //// use name <- formz.require(field("name"), defintions.text_field()) 68 + //// use name <- formz.require(field("name"), definitions.text_field()) 71 69 //// 72 70 //// formz.create_form(name) 73 71 //// } ··· 82 80 //// 83 81 //// ```gleam 84 82 //// fn make_form() { 85 - //// use greeting <- optional(field("greeting"), defintions.text_field()) 86 - //// use name <- optional(field("name"), defintions.text_field()) 83 + //// use greeting <- optional(field("greeting"), definitions.text_field()) 84 + //// use name <- optional(field("name"), definitions.text_field()) 87 85 //// 88 86 //// formz.create_form(greeting <> " " <> name) 89 87 //// } ··· 115 113 /// been added. 116 114 pub opaque type Form(widget, output) { 117 115 Form( 118 - items: List(FormItem(widget)), 119 - decode: fn(List(FormItem(widget))) -> Result(output, List(FormItem(widget))), 116 + items: List(Item(widget)), 117 + decode: fn(List(Item(widget))) -> Result(output, List(Item(widget))), 120 118 stub: output, 121 119 ) 122 120 } 123 121 124 - /// A form is a decode function and a list of `FormItem`s. You add `FormItem`s 125 - /// to a form using the `optional`, `require`, `list`, etc functions. You 126 - /// primarily only use these directly when writing a form generator function to 127 - /// output your function to HTML. 122 + /// You add an `Item` to a form using the `optional`, `require`, `list`, 123 + /// `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. 126 + /// 127 + /// You primarily only use an `Item` directly when writing a form generator 128 + /// function to output your function to HTML. 128 129 /// 129 - /// You can also manipulate these after a form has been created, to change 130 + /// You can also manipulate an `Item` after a form has been created, to change 130 131 /// things like labels, help text, etc. There are specific functions, 131 132 /// `update_field`, `update_listfield` and `update_subform`, to help with this, 132 133 /// so you don't have to pattern match when updating a specific item. ··· 135 136 /// let form = make_form() 136 137 /// form.update_field("name", field.set_label(_, "Full Name")) 137 138 /// ``` 138 - pub type FormItem(widget) { 139 + pub type Item(widget) { 139 140 /// A single field that (generally speaking) corresponds to a single 140 141 /// HTML input 141 - Field(detail: field.Field, state: FieldState, widget: widget) 142 + Field(detail: field.Field, state: InputState, widget: widget) 142 143 /// A single field that a consumer can submit multiple values for. 143 144 ListField( 144 145 detail: field.Field, 145 - states: List(FieldState), 146 - blank_fields: fn(Int) -> List(FieldState), 146 + states: List(InputState), 147 + limit_check: LimitCheck, 147 148 widget: widget, 148 149 ) 149 150 /// A group of fields that are added as and then parsed to a single unit. 150 - SubForm(detail: subform.SubForm, items: List(FormItem(widget))) 151 + SubForm(detail: subform.SubForm, items: List(Item(widget))) 151 152 } 152 153 153 - /// The state of the field, this is used to track the current raw value, 154 - /// whether the field is required or not, if the field has been validated, 155 - /// and the outcome of that validation. 156 - pub type FieldState { 157 - Unvalidated(value: String, presence: FieldPresence) 158 - Valid(value: String, presence: FieldPresence) 159 - Invalid(value: String, presence: FieldPresence, error: String) 154 + /// The state of the an input for a field. This is used to track the current 155 + /// raw value, whether a value is required or not, if the value has been 156 + /// validated, and the outcome of that validation. 157 + pub type InputState { 158 + Unvalidated(value: String, requirement: Requirement) 159 + Valid(value: String, requirement: Requirement) 160 + Invalid(value: String, requirement: Requirement, error: String) 160 161 } 161 162 162 - /// Whether a field is required or not. 163 - pub type FieldPresence { 163 + /// Whether an input value is required for an input field. 164 + pub type Requirement { 164 165 Optional 165 166 Required 166 167 } 167 168 168 - /// A `Definition` is the second argument needed to [add a field to a form](#require). 169 - /// It is what 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 [Field](https://hexdocs.pm/formz/formz/field.html); 171 - /// they take a bit more work to make as they are intended to be reusable. 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. 172 173 /// 173 - /// The first role of a `Defintion` is to generate the HTML widget for the field. 174 - /// This library is format-agnostic and you can generate HTML widgets as raw 175 - /// strings, Lustre elements, Nakai nodes, something else, etc. There are 176 - /// currently three `formz` libraries that provide common field 177 - /// definitions (and widgets) for the most common HTML inputs. 174 + /// The first role of a `Defintion` is to generate the HTML input for the field. 175 + /// This library is format-agnostic and you can generate inputs as raw 176 + /// strings, Lustre elements, Nakai nodes, something else, etc. The second role 177 + /// of a `Definition` is to parse the raw string data from the input into a 178 + /// Gleam type. 179 + /// 180 + /// There are currently three `formz` libraries that provide common field 181 + /// definitions for the most common HTML inputs: 178 182 /// 179 183 /// - [formz_string](https://hexdocs.pm/formz_string/) 180 184 /// - [formz_nakai](https://hexdocs.pm/formz_nakai/) 181 - /// - [formz_lustre](https://hexdocs.pm/formz_lustre/) (untested in a browser, 182 - /// would it be useful there??) 185 + /// - [formz_lustre](https://hexdocs.pm/formz_lustre/) 183 186 /// 184 - /// The second role of a `Definition` is to parse the data from the field. 187 + /// 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` 189 + /// was used to add it to the form). If a value is required, the definition is 190 + /// expected to return a string error if the input is empty, or the `required` type 191 + /// if it isn't. You can use the `definition` function to create a simple 192 + /// definition that just parses to an `Option` if an input is empty. 193 + /// 194 + /// However, not all fields should be parsed into an `Option` when 195 + /// given an empty input value. For example, an optional text field might be an 196 + /// empty string or an optional checkbox might be False. For these cases, you 197 + /// can use the `definition_with_custom_optional` function to create a definition 198 + /// that can parse to any type when the input is empty. 185 199 pub opaque type Definition(widget, required, optional) { 186 200 Definition( 187 201 widget: widget, ··· 202 216 ) 203 217 } 204 218 219 + /// When adding a list field to a form with `limited_list`, you have to provide 220 + /// a `LimitCheck` function that checks the number of inputs (and associated 221 + /// values) for the field. This function can either say the number of inputs 222 + /// is Ok and optionally add more, or say the number of inputs was too high 223 + /// and return an error. For example, you are presenting a blank form to a 224 + /// consumer, and you want to show three initial fields for them to fill out, 225 + /// or you want to always show one more additional field than the number of 226 + /// values that have already belong to the form, etc. 227 + /// 228 + /// There are helper functions, `limit_at_least`, `limit_at_most`, and 229 + /// `limit_between` or more generally `simple_limit_check` to make a 230 + /// `LimitCheck` function for you. I would imagine that those will cover 99.9% 231 + /// of cases and almost no one will need to write their own `LimitCheck`. But 232 + /// if you do, look at the source for `simple_limit_check` for a better idea 233 + /// of how to write one. 234 + /// 235 + /// 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. 238 + /// 239 + /// This is used multiple times... when the form is created so we know how many 240 + /// initial inputs to present, when data is added so we know if we need to add 241 + /// more inputs so users can add more items, and when the form is decoded and 242 + /// we are checking if too many fields have been added. 243 + pub type LimitCheck = 244 + fn(Int) -> Result(List(InputState), Int) 245 + 205 246 type ListParsingResult(input_output) { 206 247 ListParsingResult( 207 248 value: String, 208 - presence: FieldPresence, 249 + requirement: Requirement, 209 250 output: Result(input_output, String), 210 251 ) 211 252 } 212 253 213 - /// Create an empty form that only parses to `thing`. This is primarily 254 + /// Create an empty form that "decodes" directly to `thing`. This is 214 255 /// intended to be the final return value of a chain of callbacks that adds 215 256 /// the form's fields. 216 257 /// ··· 246 287 /// callback should be a function without side effects.** It can be called any 247 288 /// number of times. Don't do anything but create the type with the data you 248 289 /// need! If you need to do decoding that has side effects, you should use 249 - /// `decode_then_try` instead. 290 + /// `decode_then_try`. 250 291 pub fn optional( 251 292 field: field.Field, 252 293 definition: Definition(widget, _, input_output), ··· 276 317 /// callback should be a function without side effects.** It can be called any 277 318 /// number of times. Don't do anything but create the type with the data you 278 319 /// need! If you need to do decoding that has side effects, you should use 279 - /// `decode_then_try` instead. 320 + /// `decode_then_try`. 280 321 pub fn require( 281 322 field: field.Field, 282 323 is definition: Definition(widget, required_output, _), ··· 294 335 295 336 fn add_field( 296 337 field: field.Field, 297 - required: FieldPresence, 338 + requirement: Requirement, 298 339 widget: widget, 299 340 parse_field: fn(String) -> Result(input_output, String), 300 341 stub: input_output, ··· 307 348 308 349 // prepend the new field to the items from the form we got in the previous step. 309 350 let updated_items = [ 310 - Field(field, Unvalidated("", required), widget), 351 + Field(field, Unvalidated("", requirement), widget), 311 352 ..next_form.items 312 353 ] 313 354 314 355 // now create the decode function. decode function accepts most recent 315 356 // version of input list, since data can be added to it. the list 316 357 // above we just needed for the initial setup. 317 - let decode = fn(items: List(FormItem(widget))) { 358 + let decode = fn(items: List(Item(widget))) { 318 359 // pull out the latest version of this field to get latest input data 319 360 let assert [Field(field, state, widget), ..next_items] = items 320 361 ··· 336 377 // form has errors, but this field was good, so add it to the list 337 378 // of fields as is. 338 379 Error(error_items), Ok(_) -> { 339 - let f = Field(field, Valid(state.value, required), widget) 380 + let f = Field(field, Valid(state.value, requirement), widget) 340 381 Error([f, ..error_items]) 341 382 } 342 383 ··· 344 385 // mark this field as invalid, mark all the existing fields as valid, 345 386 // and return all the fields we've got so far 346 387 Ok(_), Error(error) -> { 347 - let f = Field(field, Invalid(state.value, required, error), widget) 388 + let f = Field(field, Invalid(state.value, requirement, error), widget) 348 389 Error([f, ..mark_all_fields_as_valid(next_items)]) 349 390 } 350 391 351 392 // form already has errors and this field errored, so mark this field 352 393 // as invalid, and add it to the list of errors 353 394 Error(error_items), Error(error) -> { 354 - let f = Field(field, Invalid(state.value, required, error), widget) 395 + let f = Field(field, Invalid(state.value, requirement, error), widget) 355 396 Error([f, ..error_items]) 356 397 } 357 398 } ··· 359 400 Form(items: updated_items, decode:, stub: next_form.stub) 360 401 } 361 402 362 - pub fn simple_blank_fields( 363 - min: Int, 364 - max: Int, 365 - extra: Int, 366 - ) -> fn(Int) -> List(FieldState) { 403 + /// Convenience function for creating a `LimitCheck` function. This takes 404 + /// the minimum number of required values, the maximum number of allowed values, 405 + /// and the number of "extra" blank inputs that should be offered to the user 406 + /// for filling out. 407 + /// 408 + /// If "extra" is 0, (say, to manage blank fields via javascript), then this 409 + /// will show 1 blank field initially. 410 + pub fn simple_limit_check(min: Int, max: Int, extra: Int) -> LimitCheck { 367 411 fn(num_nonempty) { 368 - int.max(1 - num_nonempty, int.min(extra, max - num_nonempty)) 369 - list.fold([1, extra, min], 0, int.max) 412 + case max - num_nonempty { 413 + x if x < 0 -> Error(x) 414 + _ -> { 415 + int.max(1 - num_nonempty, int.min(extra, max - num_nonempty)) 416 + list.fold([1, extra, min], 0, int.max) 370 417 371 - // if they've specified a minimum required, then start there 372 - let num_required = int.max(min - num_nonempty, 0) 418 + // if they've specified a minimum required, then start there 419 + let num_required = int.max(min - num_nonempty, 0) 373 420 374 - // at least one field needs to be present. don't want a form that is asking 375 - // for no input 376 - let num_base = case num_nonempty, num_required { 377 - x, y if x > 0 || y > 0 -> 0 378 - _, _ -> 1 379 - } 421 + // at least one field needs to be present. don't want a form that is asking 422 + // for no input 423 + let num_base = case num_nonempty, num_required { 424 + x, y if x > 0 || y > 0 -> 0 425 + _, _ -> 1 426 + } 380 427 381 - // they've asked for extra fields, add those unless doing so would 382 - // exceed the max 383 - let num_extra = int.min(extra, max - num_nonempty) 428 + // they've asked for extra fields, add those unless doing so would 429 + // exceed the max 430 + let num_extra = int.min(extra, max - num_nonempty) 384 431 385 - // take whatever's bigger for our optional ones, either the bare minimum 386 - // (base) or the extra if they've got room for it and they've asked for it 387 - let num_optional = int.max(num_base, num_extra) - num_required 432 + // take whatever's bigger for our optional ones, either the bare minimum 433 + // (base) or the extra if they've got room for it and they've asked for it 434 + let num_optional = int.max(num_base, num_extra) - num_required 388 435 389 - list.append( 390 - list.repeat(Unvalidated("", Required), num_required), 391 - list.repeat(Unvalidated("", Optional), num_optional), 392 - ) 436 + Ok(list.append( 437 + list.repeat(Unvalidated("", Required), num_required), 438 + list.repeat(Unvalidated("", Optional), num_optional), 439 + )) 440 + } 441 + } 393 442 } 394 443 } 395 444 396 - /// Convenience function for creating a `BlankFieldsFunc` with a minimum number 445 + /// Convenience function for creating a `LimitCheck` with a minimum number 397 446 /// of required values. This sets the maximum to `1,000,000`, effectively unlimited. 398 - pub fn limit_at_least(min: Int) { 399 - simple_blank_fields(min, 1_000_000, 1) 447 + pub fn limit_at_least(min: Int) -> LimitCheck { 448 + simple_limit_check(min, 1_000_000, 1) 400 449 } 401 450 402 - /// Convenience function for creating a `BlankFieldsFunc` with a maximum number 451 + /// Convenience function for creating a `LimitCheck` with a maximum number 403 452 /// of accepted values. This sets the minimum to `0`. 404 - pub fn limit_at_most(max: Int) { 405 - simple_blank_fields(0, max, 1) 453 + pub fn limit_at_most(max: Int) -> LimitCheck { 454 + simple_limit_check(0, max, 1) 406 455 } 407 456 408 - /// Convenience function for creating a `BlankFieldsFunc` with a minimum and maximum 457 + /// Convenience function for creating a `LimitCheck` with a minimum and maximum 409 458 /// number of values. 410 - pub fn limit_between(min: Int, max: Int) { 411 - simple_blank_fields(min, max, 1) 459 + pub fn limit_between(min: Int, max: Int) -> LimitCheck { 460 + simple_limit_check(min, max, 1) 412 461 } 413 462 414 463 /// Add a list field to a form, but with limits on the number of values that 415 - /// can be submitted. 416 - /// 417 - /// When adding a list field to a form, you have to provide a function that 418 - /// tells the form if and how many blank fields to provide for the 419 - /// consumer to fill out. 420 - /// 421 - /// For example, you are presenting an empty form to a consumer, and you 422 - /// want to show three blank fields for them to fill out, or you want to always 423 - /// show one more blank field than the number of values that already belong to 424 - /// the form, etc. 425 - /// 426 - /// This function takes as its only argument, the number of fields that already 427 - /// have a value. It should return a list of `Unvalidated` `FieldState` items 428 - /// that specify if the value is required or not. 429 - /// 430 - /// There are helper functions, `limit_at_least`, `limit_at_most`, and 431 - /// `limit_between` or more generally `simple_blank_fields` for the most common 432 - /// use cases. 464 + /// can be submitted. The `limit_check` function is used to impose those 465 + /// limits, and the `limit_at_least`, `limit_at_most`, and `limit_between` 466 + /// functions help you create this function for the most likely scenarios. 433 467 /// 434 468 /// The final argument is a callback that will be called when the form 435 469 /// is being... constructed to look for more fields; validated to check for ··· 437 471 /// callback should be a function without side effects.** It can be called any 438 472 /// number of times. Don't do anything but create the type with the data you 439 473 /// need! If you need to do decoding that has side effects, you should use 440 - /// `decode_then_try` instead. 474 + /// `decode_then_try`. 475 + /// 476 + ///### Example 477 + /// 478 + /// ```gleam 479 + /// fn make_form() { 480 + /// use names <- formz.limited_list(formz.limit_at_most(4), field("name"), definitions.text_field()) 481 + /// // names is a List(String) 482 + /// formz.create_form(name) 483 + /// } 441 484 pub fn limited_list( 442 - blank_fields: fn(Int) -> List(FieldState), 485 + limit_check: fn(Int) -> Result(List(InputState), Int), 443 486 field: field.Field, 444 487 is definition: Definition(widget, required_output, _), 445 488 next next: fn(List(required_output)) -> Form(widget, form_output), ··· 449 492 // from the form. 450 493 let next_form = next([definition.stub]) 451 494 495 + let initial_fields = limit_check(0) |> result.unwrap([]) 452 496 // prepend the new field to the items from the form we got in the previous step. 453 497 let updated_items = [ 454 - ListField(field, blank_fields(0), blank_fields, definition.widget), 498 + ListField(field, initial_fields, limit_check, definition.widget), 455 499 ..next_form.items 456 500 ] 457 501 458 502 // now create the decode function. decode function accepts most recent 459 503 // version of input list, since data can be added to it. the list 460 504 // above we just needed for the initial setup. 461 - let decode = fn(items: List(FormItem(widget))) { 505 + let decode = fn(items: List(Item(widget))) { 462 506 // pull out the latest version of this field to get latest input data 463 - let assert [ListField(field, states, blank_fields, widget), ..next_items] = 507 + let assert [ListField(field, states, limit_check, widget), ..next_items] = 464 508 items 465 509 466 510 // go through all decode all input values. these can have empty rows ··· 489 533 490 534 // form has errors, but this field was good, so mark all states as valid 491 535 Error(error_items), Ok(_) -> { 492 - let states = states |> list.map(fn(s) { Valid(s.value, s.presence) }) 493 - let f = ListField(field, states, blank_fields, widget) 536 + let states = states |> list.map(fn(s) { Valid(s.value, s.requirement) }) 537 + let f = ListField(field, states, limit_check, widget) 494 538 Error([f, ..error_items]) 495 539 } 496 540 ··· 499 543 // valid, and then return all these fields we've got so far 500 544 Ok(_), Error(_) -> { 501 545 let states = list.map(item_results, state_from_parse_result) 502 - let f = ListField(field, states, blank_fields, widget) 546 + let f = ListField(field, states, limit_check, widget) 503 547 Error([f, ..mark_all_fields_as_valid(next_items)]) 504 548 } 505 549 ··· 507 551 // to the list of errors, but first marking the invalid states as... invalid 508 552 Error(error_items), Error(_) -> { 509 553 let states = list.map(item_results, state_from_parse_result) 510 - let f = ListField(field, states, blank_fields, widget) 554 + let f = ListField(field, states, limit_check, widget) 511 555 Error([f, ..error_items]) 512 556 } 513 557 } ··· 518 562 519 563 fn state_from_parse_result( 520 564 result: ListParsingResult(input_output), 521 - ) -> FieldState { 522 - let ListParsingResult(value, presence, output) = result 565 + ) -> InputState { 566 + let ListParsingResult(value, requirement, output) = result 523 567 case output { 524 - Ok(_) -> Valid(value, presence) 525 - Error(error) -> Invalid(value, presence, error) 568 + Ok(_) -> Valid(value, requirement) 569 + Error(error) -> Invalid(value, requirement, error) 526 570 } 527 571 } 528 572 ··· 530 574 states: List(ListParsingResult(output)), 531 575 ) -> List(Result(output, String)) { 532 576 list.filter_map(states, fn(result) { 533 - case result.value, result.presence { 577 + case result.value, result.requirement { 534 578 "", Optional -> Error(Nil) 535 579 _, _ -> Ok(result.output) 536 580 } ··· 538 582 } 539 583 540 584 fn parse_list_state( 541 - state: FieldState, 585 + state: InputState, 542 586 parse: fn(String) -> Result(a, String), 543 587 stub: a, 544 588 ) -> ListParsingResult(a) { 545 - case state.value, state.presence { 589 + case state.value, state.requirement { 546 590 "", Required -> parse(state.value) 547 591 "", Optional -> Ok(stub) 548 592 _, _ -> parse(state.value) 549 593 } 550 - |> ListParsingResult(state.value, state.presence, _) 594 + |> ListParsingResult(state.value, state.requirement, _) 551 595 } 552 596 553 597 /// Add a list field to a form, but with no limits on the number of values that ··· 560 604 /// callback should be a function without side effects.** It can be called any 561 605 /// number of times. Don't do anything but create the type with the data you 562 606 /// need! If you need to do decoding that has side effects, you should use 563 - /// `decode_then_try` instead. 607 + /// `decode_then_try`. 564 608 pub fn list( 565 609 field: field.Field, 566 610 is definition: Definition(widget, required_output, _), ··· 569 613 limited_list(limit_at_most(1_000_000), field, definition, next) 570 614 } 571 615 572 - fn add_prefix_to_item( 573 - item: FormItem(widget), 574 - prefix: String, 575 - ) -> FormItem(widget) { 616 + fn add_prefix_to_item(item: Item(widget), prefix: String) -> Item(widget) { 576 617 case item { 577 618 Field(item_details, state, widget) -> { 578 619 let name = prefix <> "." <> item_details.name 579 620 Field(item_details |> field.set_name(name), state, widget) 580 621 } 581 - ListField(item_details, states, blank_fields, widget) -> { 622 + ListField(item_details, states, limit_check, widget) -> { 582 623 let name = prefix <> "." <> item_details.name 583 624 ListField( 584 625 item_details |> field.set_name(name), 585 626 states, 586 - blank_fields, 627 + limit_check, 587 628 widget, 588 629 ) 589 630 } ··· 605 646 /// callback should be a function without side effects.** It can be called any 606 647 /// number of times. Don't do anything but create the type with the data you 607 648 /// need! If you need to do decoding that has side effects, you should use 608 - /// `decode_then_try` instead. 649 + /// `decode_then_try`. 609 650 pub fn subform( 610 651 subform: subform.SubForm, 611 652 form: Form(widget, sub_output), ··· 617 658 let subform = SubForm(subform, sub_items) 618 659 let updated_items = [subform, ..next_form.items] 619 660 620 - let decode = fn(items: List(FormItem(widget))) { 661 + let decode = fn(items: List(Item(widget))) { 621 662 // pull out the latest version of this field to get latest input data 622 663 let assert [SubForm(details, sub_items), ..next_items] = items 623 664 ··· 657 698 } 658 699 659 700 /// Add input data to this form. This will set the raw string value of the fields. 660 - /// It does not trigger any parsing, so you can also use this to set default values 661 - /// (if you do it in your form generator function) or initial values (if you do it 662 - /// before rendering an empty form). 701 + /// It does not trigger any parsing or decoding, so you can also use this to set 702 + /// default values (if you do it in your form generator function) or initial values 703 + /// (if you do it before rendering a blank form). 663 704 /// 664 705 /// The input data is a list of tuples, where the first element is the name of the 665 706 /// field and the second element is the value to set. If the field does not exist ··· 677 718 } 678 719 679 720 fn do_data( 680 - items: List(FormItem(widget)), 721 + items: List(Item(widget)), 681 722 data: Dict(String, List(String)), 682 - ) -> List(FormItem(widget)) { 723 + ) -> List(Item(widget)) { 683 724 list.map(items, fn(item) { 684 725 let values = dict.get(data, get_item_name(item)) 685 726 case item, values { 686 727 Field(detail, state, widget), Ok([_, ..] as values) -> { 687 728 let assert Ok(last) = list.last(values) 688 - Field(detail, Unvalidated(last, state.presence), widget) 729 + Field(detail, Unvalidated(last, state.requirement), widget) 689 730 } 690 - ListField(detail, states, blank_fields, widget), Ok(values) -> { 731 + ListField(detail, states, limit_check, widget), Ok(values) -> { 691 732 let nonempty = list.filter(values, fn(v) { !string.is_empty(v) }) 692 733 let num_nonempty = list.length(nonempty) 693 734 735 + let additional_fields = limit_check(num_nonempty) |> result.unwrap([]) 694 736 let items = 695 - list.map2(states, nonempty, fn(s, v) { Unvalidated(v, s.presence) }) 737 + list.map2(states, nonempty, fn(s, v) { Unvalidated(v, s.requirement) }) 696 738 |> list.append( 697 739 nonempty 698 740 |> list.drop(list.length(states)) 699 741 |> list.map(fn(v) { Unvalidated(v, Optional) }), 700 742 ) 701 - |> list.append(blank_fields(num_nonempty)) 743 + |> list.append(additional_fields) 702 744 703 - ListField(detail, items, blank_fields, widget) 745 + ListField(detail, items, limit_check, widget) 704 746 } 705 747 SubForm(detail, items), _ -> SubForm(detail, do_data(items, data)) 706 748 _, _ -> item ··· 738 780 } 739 781 } 740 782 741 - /// Parse the form, then apply a function to the output if it was successful. 783 + /// Decode the form, then apply a function to the output if it was successful. 742 784 /// This is a very thin wrapper around `decode` and `result.try`, but the 743 785 /// difference being it will pass the form along to the function as a second 744 786 /// argument in addition to the successful result. This allows you to easily ··· 774 816 }) 775 817 } 776 818 777 - fn mark_all_fields_as_valid( 778 - items: List(FormItem(widget)), 779 - ) -> List(FormItem(widget)) { 819 + fn mark_all_fields_as_valid(items: List(Item(widget))) -> List(Item(widget)) { 780 820 list.map(items, fn(item) { 781 821 case item { 782 822 Field(field, state, widget) -> 783 - Field(field, Valid(state.value, state.presence), widget) 784 - ListField(field, states, blank_fields, widget) -> { 823 + Field(field, Valid(state.value, state.requirement), widget) 824 + ListField(field, states, limit_check, widget) -> { 785 825 let new_states = 786 826 states 787 - |> list.map(fn(state) { Valid(state.value, state.presence) }) 788 - ListField(field, new_states, blank_fields, widget) 827 + |> list.map(fn(state) { Valid(state.value, state.requirement) }) 828 + ListField(field, new_states, limit_check, widget) 789 829 } 790 830 SubForm(subform, items) -> 791 831 SubForm(subform, mark_all_fields_as_valid(items)) ··· 846 886 form.items |> list.map(get_item_name) |> validate(form, _) 847 887 } 848 888 849 - /// Get each [`FormItem`](https://hexdocs.pm/formz/formz.html#FormItem) added 850 - /// to the form. Any time a field, list field, or subform are added, a FormItem is created. 851 - pub fn items(form: Form(widget, output)) -> List(FormItem(widget)) { 889 + /// Get each [`Item`](https://hexdocs.pm/formz/formz.html#Item) added 890 + /// to the form. Any time a field, list field, or subform are added, a `Item` 891 + /// is created. Use this to loop through all the fields of your form and 892 + /// generate HTML for them. 893 + pub fn items(form: Form(widget, output)) -> List(Item(widget)) { 852 894 form.items 853 895 } 854 896 855 - /// Get the [`FormItem`](https://hexdocs.pm/formz/formz.html#FormItem) with the 897 + /// Get the [`Item`](https://hexdocs.pm/formz/formz.html#Item) with the 856 898 /// given name. If multiple items have the same name, the first one is returned. 857 899 pub fn get( 858 900 form: Form(widget, output), 859 901 name: String, 860 - ) -> Result(FormItem(widget), Nil) { 902 + ) -> Result(Item(widget), Nil) { 861 903 list.find(form.items, fn(item) { name == get_item_name(item) }) 862 904 } 863 905 864 - /// Update the [`FormItem`](https://hexdocs.pm/formz/formz.html#FormItem) with 906 + /// Update the [`Item`](https://hexdocs.pm/formz/formz.html#Item) with 865 907 /// the given name using the provided function. If multiple items have the same 866 908 /// name, it will be called on all of them. 867 909 pub fn update( 868 910 form: Form(widget, output), 869 911 name: String, 870 - fun: fn(FormItem(widget)) -> FormItem(widget), 912 + fun: fn(Item(widget)) -> Item(widget), 871 913 ) { 872 914 let items = do_update(form.items, name, fun) 873 915 Form(..form, items:) 874 916 } 875 917 876 918 fn do_update( 877 - items: List(FormItem(widget)), 919 + items: List(Item(widget)), 878 920 name: String, 879 - fun: fn(FormItem(widget)) -> FormItem(widget), 880 - ) -> List(FormItem(widget)) { 921 + fun: fn(Item(widget)) -> Item(widget), 922 + ) -> List(Item(widget)) { 881 923 list.map(items, fn(item) { 882 924 case item { 883 925 Field(detail, _, _) if detail.name == name -> fun(item) ··· 894 936 }) 895 937 } 896 938 897 - /// Update the [`Field`](https://hexdocs.pm/formz/formz/field.html) with 939 + /// Update the `Field` [details](https://hexdocs.pm/formz/formz/field.html) with 898 940 /// the given name using the provided function. If multiple items have the same 899 941 /// name, it will be called on all of them. If no items have the given name, 900 942 /// or an item with the given name exists but isn't a `Field`, this function ··· 902 944 /// 903 945 /// ```gleam 904 946 /// let form = make_form() 905 - /// update(form, "name", field.set_label(_, "Full Name")) 947 + /// update_field(form, "name", field.set_label(_, "Full Name")) 906 948 /// ``` 907 949 pub fn update_field( 908 950 form: Form(widget, output), ··· 917 959 }) 918 960 } 919 961 920 - /// Update the [`ListField`](https://hexdocs.pm/formz/formz/field.html) with 962 + /// Update the `ListField` [details]](https://hexdocs.pm/formz/formz/field.html) with 921 963 /// the given name using the provided function. If multiple items have the same 922 964 /// name, it will be called on all of them. If no items have the given name, 923 965 /// or an item with the given name exists but isn't a `ListField`, this function ··· 963 1005 }) 964 1006 } 965 1007 1008 + /// Convenience function for setting the `InputState` of a field to 1009 + /// Invalid with a given error message. 1010 + /// 1011 + /// ### Example 1012 + /// 1013 + /// ``` 1014 + /// set_field_error(form, "username", "Username is taken") 1015 + /// ``` 966 1016 pub fn set_field_error( 967 1017 form: Form(widget, output), 968 1018 name: String, ··· 971 1021 update(form, name, fn(item) { 972 1022 case item { 973 1023 Field(field, state, widget) -> 974 - Field(field, Invalid(state.value, state.presence, str), widget) 1024 + Field(field, Invalid(state.value, state.requirement, str), widget) 975 1025 _ -> item 976 1026 } 977 1027 }) 978 1028 } 979 1029 1030 + /// Convenience function for setting the `InputState`s of a list field. This 1031 + /// takes a list of Results, where the Ok means the input is `Valid` and 1032 + /// `Error` means the input is `Invalid` with the given error message. 1033 + /// 1034 + /// ### Example 1035 + /// 1036 + /// ``` 1037 + /// set_listfield_errors(form, "pet_names", [Ok(Nil), Ok(Nil), Error("Must be a cat")]) 1038 + /// ``` 980 1039 pub fn set_listfield_errors( 981 1040 form: Form(widget, output), 982 1041 name: String, ··· 984 1043 ) -> Form(widget, output) { 985 1044 update(form, name, fn(item) { 986 1045 case item { 987 - ListField(field, states, blank_fields, widget) -> { 1046 + ListField(field, states, limit_check, widget) -> { 988 1047 let invalid_states = 989 1048 list.map2(states, errors, fn(state, err) { 990 1049 case err { 991 - Error(str) -> Invalid(state.value, state.presence, str) 1050 + Error(str) -> Invalid(state.value, state.requirement, str) 992 1051 Ok(Nil) -> state 993 1052 } 994 1053 }) 995 - ListField(field, invalid_states, blank_fields, widget) 1054 + ListField(field, invalid_states, limit_check, widget) 996 1055 } 997 1056 _ -> item 998 1057 } 999 1058 }) 1000 1059 } 1001 1060 1002 - fn get_item_name(item: FormItem(widget)) -> String { 1061 + fn get_item_name(item: Item(widget)) -> String { 1003 1062 case item { 1004 1063 Field(field, _, _) -> field.name 1005 1064 ListField(field, _, _, _) -> field.name ··· 1008 1067 } 1009 1068 1010 1069 /// Create a simple `Definition` that is parsed as an `Option` if the field 1011 - /// is empty. 1070 + /// is empty. See [formz_string](https://hexdocs.pm/formz_string/formz_string/definitions.html) 1071 + /// for more examples of making widgets and definitions. 1012 1072 pub fn definition( 1013 1073 widget widget: widget, 1014 1074 parse parse: fn(String) -> Result(required, String), ··· 1028 1088 ) 1029 1089 } 1030 1090 1091 + /// Create a `Definition` that can parse to any type if the field is optional. 1092 + /// This takes two functions. The first, `parse`, is the "required"" parse 1093 + /// function, which takes the raw string value, and turns it into the required 1094 + /// type. The second, `optional_parse`, is a function that takes the normal 1095 + /// parse function and the raw string value, and it is supposed to check the 1096 + /// input string: if it is empty, return an `Ok` with the `optional_stub` 1097 + /// value; and if it's not empty use the normal parse function. 1098 + /// 1099 + /// See [formz_string](https://hexdocs.pm/formz_string/formz_string/definitions.html) 1100 + /// for more examples of making widgets and definitions. 1031 1101 pub fn definition_with_custom_optional( 1032 1102 widget widget: widget, 1033 1103 parse parse: fn(String) -> Result(required, String), ··· 1064 1134 Definition(..def, parse: fn(val) { val |> def.parse |> result.try(fun) }) 1065 1135 } 1066 1136 1067 - pub fn transform( 1068 - def: Definition(widget, old_required, old_optional), 1069 - transform: fn(old_required) -> Result(new_required, String), 1070 - stub: new_required, 1071 - ) -> Definition(widget, new_required, option.Option(new_required)) { 1072 - let Definition(widget, parse, _, _, _) = def 1073 - Definition( 1074 - widget:, 1075 - parse: fn(val) { val |> parse |> result.try(transform) }, 1076 - stub:, 1077 - optional_parse: fn(parse, str) { 1078 - case str { 1079 - "" -> Ok(option.None) 1080 - _ -> parse(str) |> result.map(option.Some) 1081 - } 1082 - }, 1083 - optional_stub: option.None, 1084 - ) 1085 - } 1137 + // pub fn transform( 1138 + // def: Definition(widget, old_required, old_optional), 1139 + // transform: fn(old_required) -> Result(new_required, String), 1140 + // stub: new_required, 1141 + // ) -> Definition(widget, new_required, option.Option(new_required)) { 1142 + // let Definition(widget, parse, _, _, _) = def 1143 + // Definition( 1144 + // widget:, 1145 + // parse: fn(val) { val |> parse |> result.try(transform) }, 1146 + // stub:, 1147 + // optional_parse: fn(parse, str) { 1148 + // case str { 1149 + // "" -> Ok(option.None) 1150 + // _ -> parse(str) |> result.map(option.Some) 1151 + // } 1152 + // }, 1153 + // optional_stub: option.None, 1154 + // ) 1155 + // } 1086 1156 1087 - pub fn transform_with_custom_optional( 1088 - def: Definition(widget, old_required, old_optional), 1089 - transform: fn(old_required) -> Result(new_required, String), 1090 - stub: new_required, 1091 - optional_parse: fn(fn(String) -> Result(new_required, String), String) -> 1092 - Result(new_optional, String), 1093 - optional_stub: new_optional, 1094 - ) -> Definition(widget, new_required, new_optional) { 1095 - let Definition(widget, parse, _, _, _) = def 1096 - Definition( 1097 - widget:, 1098 - parse: fn(val) { val |> parse |> result.try(transform) }, 1099 - stub:, 1100 - optional_parse:, 1101 - optional_stub:, 1102 - ) 1103 - } 1157 + // pub fn transform_with_custom_optional( 1158 + // def: Definition(widget, old_required, old_optional), 1159 + // transform: fn(old_required) -> Result(new_required, String), 1160 + // stub: new_required, 1161 + // optional_parse: fn(fn(String) -> Result(new_required, String), String) -> 1162 + // Result(new_optional, String), 1163 + // optional_stub: new_optional, 1164 + // ) -> Definition(widget, new_required, new_optional) { 1165 + // let Definition(widget, parse, _, _, _) = def 1166 + // Definition( 1167 + // widget:, 1168 + // parse: fn(val) { val |> parse |> result.try(transform) }, 1169 + // stub:, 1170 + // optional_parse:, 1171 + // optional_stub:, 1172 + // ) 1173 + // } 1104 1174 1175 + /// Update the widget of a definition. 1105 1176 pub fn widget( 1106 1177 def: Definition(widget, a, b), 1107 1178 widget: widget, ··· 1124 1195 } 1125 1196 1126 1197 @internal 1127 - pub fn get_states(form: Form(widget, output)) -> List(FieldState) { 1198 + pub fn get_states(form: Form(widget, output)) -> List(InputState) { 1128 1199 form.items |> do_get_states |> list.reverse 1129 1200 } 1130 1201 1131 - fn do_get_states(items: List(FormItem(widget))) -> List(FieldState) { 1202 + fn do_get_states(items: List(Item(widget))) -> List(InputState) { 1132 1203 list.fold(items, [], fn(acc, item) { 1133 1204 case item { 1134 1205 Field(_, state, _) -> [state, ..acc]
+4 -5
formz/test/formz_test.gleam
··· 2 2 import formz/field.{field} 3 3 import formz/subform.{subform} 4 4 import formz/validation 5 - import gleam/option 6 5 import gleam/string 7 6 import gleeunit 8 7 import gleeunit/should ··· 53 52 ) 54 53 } 55 54 56 - fn state_should_be(state: formz.FieldState, expected: formz.FieldState) { 55 + fn state_should_be(state: formz.InputState, expected: formz.InputState) { 57 56 should.equal(state, expected) 58 57 } 59 58 ··· 602 601 pub fn limited_list_test() { 603 602 let zero_extra = { 604 603 use a <- formz.limited_list( 605 - formz.simple_blank_fields(1, 4, 0), 604 + formz.simple_limit_check(1, 4, 0), 606 605 field("a"), 607 606 integer_field(), 608 607 ) ··· 611 610 } 612 611 let one_extra = { 613 612 use a <- formz.limited_list( 614 - formz.simple_blank_fields(1, 4, 1), 613 + formz.simple_limit_check(1, 4, 1), 615 614 field("a"), 616 615 integer_field(), 617 616 ) ··· 621 620 622 621 let two_extra = { 623 622 use a <- formz.limited_list( 624 - formz.simple_blank_fields(1, 4, 2), 623 + formz.simple_limit_check(1, 4, 2), 625 624 field("a"), 626 625 integer_field(), 627 626 )
+5 -5
formz_demo/src/formz_demo/example/page.gleam
··· 46 46 } 47 47 48 48 fn get_fields(form: formz.Form(format, ouput)) { 49 - form |> formz.items |> do_get_fields([]) |> list.reverse 49 + form |> formz.items |> do_get_items([]) |> list.reverse 50 50 } 51 51 52 - fn do_get_fields(items: List(formz.FormItem(format)), acc) { 52 + fn do_get_items(items: List(formz.Item(format)), acc) { 53 53 case items { 54 54 [] -> acc 55 55 [formz.Field(field, state, _), ..rest] -> 56 - do_get_fields(rest, [#(field, state), ..acc]) 56 + do_get_items(rest, [#(field, state), ..acc]) 57 57 [formz.ListField(field, states, _, _), ..rest] -> { 58 58 let tups = list.map(states, fn(s) { #(field, s) }) |> list.reverse 59 - do_get_fields(rest, list.append(tups, acc)) 59 + do_get_items(rest, list.append(tups, acc)) 60 60 } 61 61 [formz.SubForm(_, items), ..rest] -> 62 - do_get_fields(list.flatten([items, rest]), acc) 62 + do_get_items(list.flatten([items, rest]), acc) 63 63 } 64 64 } 65 65
+1 -1
formz_lustre/src/formz_lustre/simple.gleam
··· 14 14 } 15 15 16 16 pub fn generate_item( 17 - item: formz.FormItem(widget.Widget(msg)), 17 + item: formz.Item(widget.Widget(msg)), 18 18 ) -> element.Element(msg) { 19 19 case item { 20 20 formz.Field(field, state, _) if field.hidden == True ->
+1 -1
formz_lustre/src/formz_lustre/widget.gleam
··· 13 13 import lustre/element 14 14 15 15 pub type Widget(msg) = 16 - fn(field.Field, formz.FieldState, Args) -> element.Element(msg) 16 + fn(field.Field, formz.InputState, Args) -> element.Element(msg) 17 17 18 18 pub type Args { 19 19 Args(
+19 -19
formz_lustre/src/formz_lustre/widgets.gleam
··· 62 62 } 63 63 } 64 64 65 - fn required_attr(presence: formz.FieldPresence) -> attribute.Attribute(msg) { 66 - case presence { 65 + fn required_attr(requirement: formz.Requirement) -> attribute.Attribute(msg) { 66 + case requirement { 67 67 formz.Required -> attribute.required(True) 68 68 formz.Optional -> attribute.none() 69 69 } ··· 93 93 /// Create an `<input type="checkbox">`. The checkbox is checked 94 94 /// if the value is "on" (the browser default). 95 95 pub fn checkbox_widget() { 96 - fn(field: Field, state: formz.FieldState, args: widget.Args) { 96 + fn(field: Field, state: formz.InputState, args: widget.Args) { 97 97 let value = state.value 98 98 let state = case state { 99 - formz.Unvalidated(_, presence) -> formz.Unvalidated("", presence) 100 - formz.Valid(_, presence) -> formz.Valid("", presence) 101 - formz.Invalid(_, presence, e) -> formz.Invalid("", presence, e) 99 + formz.Unvalidated(_, requirement) -> formz.Unvalidated("", requirement) 100 + formz.Valid(_, requirement) -> formz.Valid("", requirement) 101 + formz.Invalid(_, requirement, e) -> formz.Invalid("", requirement, e) 102 102 } 103 103 do_input_widget(field, state, args, "checkbox", [checked_attr(value)]) 104 104 } ··· 111 111 /// the step size. If you truly need any float, then a `type="text"` input might be a 112 112 /// better choice. 113 113 pub fn number_widget(step_size: String) { 114 - fn(field: Field, state: formz.FieldState, args: widget.Args) { 114 + fn(field: Field, state: formz.InputState, args: widget.Args) { 115 115 do_input_widget(field, state, args, "number", [step_size_attr(step_size)]) 116 116 } 117 117 } ··· 119 119 /// Create an `<input type="password">`. This will not output the value in the 120 120 /// generated HTML for privacy/security concerns. 121 121 pub fn password_widget() { 122 - fn(field: Field, state: formz.FieldState, args: widget.Args) { 122 + fn(field: Field, state: formz.InputState, args: widget.Args) { 123 123 let state = case state { 124 - formz.Unvalidated(_, presence) -> formz.Unvalidated("", presence) 125 - formz.Valid(_, presence) -> formz.Valid("", presence) 126 - formz.Invalid(_, presence, e) -> formz.Invalid("", presence, e) 124 + formz.Unvalidated(_, requirement) -> formz.Unvalidated("", requirement) 125 + formz.Valid(_, requirement) -> formz.Valid("", requirement) 126 + formz.Invalid(_, requirement, e) -> formz.Invalid("", requirement, e) 127 127 } 128 128 do_input_widget(field, state, args, "password", []) 129 129 } ··· 132 132 /// Generate any `<input>` like `type="text"`, `type="email"` or 133 133 /// `type="url"`. 134 134 pub fn input_widget(type_: String) { 135 - fn(field: Field, state: formz.FieldState, args: widget.Args) { 135 + fn(field: Field, state: formz.InputState, args: widget.Args) { 136 136 do_input_widget(field, state, args, type_, []) 137 137 } 138 138 } 139 139 140 140 fn do_input_widget( 141 141 field: Field, 142 - state: formz.FieldState, 142 + state: formz.InputState, 143 143 args: widget.Args, 144 144 type_: String, 145 145 extra_attrs: List(attribute.Attribute(msg)), ··· 150 150 attribute.type_(type_), 151 151 name_attr(field.name), 152 152 id_attr(args.id), 153 - required_attr(state.presence), 153 + required_attr(state.requirement), 154 154 disabled_attr(field.disabled), 155 155 value_attr(state.value), 156 156 aria_label_attr(args.labelled_by, field.label), ··· 163 163 164 164 /// Create a `<textarea></textarea>`. 165 165 pub fn textarea_widget() { 166 - fn(field: Field, state: formz.FieldState, args: widget.Args) -> element.Element( 166 + fn(field: Field, state: formz.InputState, args: widget.Args) -> element.Element( 167 167 msg, 168 168 ) { 169 169 html.textarea( 170 170 [ 171 171 name_attr(field.name), 172 172 id_attr(args.id), 173 - required_attr(state.presence), 173 + required_attr(state.requirement), 174 174 aria_label_attr(args.labelled_by, field.label), 175 175 aria_describedby_attr(args.described_by), 176 176 ], ··· 183 183 /// passing data around and you don't want it to be visible to the user. Like 184 184 /// say, the ID of a record being edited. 185 185 pub fn hidden_widget() { 186 - fn(field: Field, state: formz.FieldState, _args: widget.Args) -> element.Element( 186 + fn(field: Field, state: formz.InputState, _args: widget.Args) -> element.Element( 187 187 msg, 188 188 ) { 189 189 html.input([ ··· 198 198 /// of variants is a two-tuple, where the first item is the text to display and 199 199 /// the second item is the value. 200 200 pub fn select_widget(variants: List(#(String, String))) { 201 - fn(field: Field, state: formz.FieldState, args: widget.Args) -> element.Element( 201 + fn(field: Field, state: formz.InputState, args: widget.Args) -> element.Element( 202 202 msg, 203 203 ) { 204 204 html.select( 205 205 [ 206 206 name_attr(field.name), 207 207 id_attr(args.id), 208 - required_attr(state.presence), 208 + required_attr(state.requirement), 209 209 aria_label_attr(args.labelled_by, field.label), 210 210 aria_describedby_attr(args.described_by), 211 211 ],
+2 -2
formz_lustre/test/formz_lustre/widgets_test.gleam
··· 49 49 let string_field = field.Field(name:, label:, help_text:, hidden:, disabled:) 50 50 let field = field.Field(name:, label:, help_text:, hidden:, disabled:) 51 51 52 - let presence = case required { 52 + let requirement = case required { 53 53 True -> formz.Required 54 54 False -> formz.Optional 55 55 } 56 - let state = formz.Valid(value, presence) 56 + let state = formz.Valid(value, requirement) 57 57 58 58 widget(field, state, args) 59 59 |> convert_to_string
+2 -2
formz_nakai/src/formz_nakai/simple.gleam
··· 4 4 import nakai/attr 5 5 import nakai/html 6 6 7 - pub fn generate(form) -> html.Node { 7 + pub fn generate(form: formz.Form(widget.Widget, a)) -> html.Node { 8 8 form 9 9 |> formz.items 10 10 |> list.map(generate_visible_item) 11 11 |> html.div([attr.class("formz_items")], _) 12 12 } 13 13 14 - pub fn generate_visible_item(item: formz.FormItem(widget.Widget)) -> html.Node { 14 + pub fn generate_visible_item(item: formz.Item(widget.Widget)) -> html.Node { 15 15 case item { 16 16 formz.Field(field, state, _) if field.hidden == True -> 17 17 html.input([
+1 -1
formz_nakai/src/formz_nakai/widget.gleam
··· 13 13 import nakai/html 14 14 15 15 pub type Widget = 16 - fn(field.Field, formz.FieldState, Args) -> html.Node 16 + fn(field.Field, formz.InputState, Args) -> html.Node 17 17 18 18 pub type Args { 19 19 Args(
+19 -19
formz_nakai/src/formz_nakai/widgets.gleam
··· 62 62 } 63 63 } 64 64 65 - fn required_attr(presence: formz.FieldPresence) -> List(attr.Attr) { 66 - case presence { 65 + fn required_attr(requirement: formz.Requirement) -> List(attr.Attr) { 66 + case requirement { 67 67 formz.Required -> [attr.required("")] 68 68 formz.Optional -> [] 69 69 } ··· 93 93 /// Create an `<input type="checkbox">`. The checkbox is checked 94 94 /// if the value is "on" (the browser default). 95 95 pub fn checkbox_widget() { 96 - fn(field: Field, state: formz.FieldState, args: widget.Args) { 96 + fn(field: Field, state: formz.InputState, args: widget.Args) { 97 97 let value = state.value 98 98 let state = case state { 99 - formz.Unvalidated(_, presence) -> formz.Unvalidated("", presence) 100 - formz.Valid(_, presence) -> formz.Valid("", presence) 101 - formz.Invalid(_, presence, e) -> formz.Invalid("", presence, e) 99 + formz.Unvalidated(_, requirement) -> formz.Unvalidated("", requirement) 100 + formz.Valid(_, requirement) -> formz.Valid("", requirement) 101 + formz.Invalid(_, requirement, e) -> formz.Invalid("", requirement, e) 102 102 } 103 103 do_input_widget(field, state, args, "checkbox", [checked_attr(value)]) 104 104 } ··· 111 111 /// the step size. If you truly need any float, then a `type="text"` input might be a 112 112 /// better choice. 113 113 pub fn number_widget(step_size: String) { 114 - fn(field: Field, state: formz.FieldState, args: widget.Args) { 114 + fn(field: Field, state: formz.InputState, args: widget.Args) { 115 115 do_input_widget(field, state, args, "number", [step_size_attr(step_size)]) 116 116 } 117 117 } ··· 119 119 /// Create an `<input type="password">`. This will not output the value in the 120 120 /// generated HTML for privacy/security concerns. 121 121 pub fn password_widget() { 122 - fn(field: Field, state: formz.FieldState, args: widget.Args) { 122 + fn(field: Field, state: formz.InputState, args: widget.Args) { 123 123 let state = case state { 124 - formz.Unvalidated(_, presence) -> formz.Unvalidated("", presence) 125 - formz.Valid(_, presence) -> formz.Valid("", presence) 126 - formz.Invalid(_, presence, e) -> formz.Invalid("", presence, e) 124 + formz.Unvalidated(_, requirement) -> formz.Unvalidated("", requirement) 125 + formz.Valid(_, requirement) -> formz.Valid("", requirement) 126 + formz.Invalid(_, requirement, e) -> formz.Invalid("", requirement, e) 127 127 } 128 128 do_input_widget(field, state, args, "password", []) 129 129 } ··· 132 132 /// Generate any `<input>` like `type="text"`, `type="email"` or 133 133 /// `type="url"`. 134 134 pub fn input_widget(type_: String) { 135 - fn(field: Field, state: formz.FieldState, args: widget.Args) { 135 + fn(field: Field, state: formz.InputState, args: widget.Args) { 136 136 do_input_widget(field, state, args, type_, []) 137 137 } 138 138 } 139 139 140 140 fn do_input_widget( 141 141 field: Field, 142 - state: formz.FieldState, 142 + state: formz.InputState, 143 143 args: widget.Args, 144 144 type_: String, 145 145 extra_attrs: List(List(attr.Attr)), ··· 149 149 type_attr(type_), 150 150 name_attr(field.name), 151 151 id_attr(args.id), 152 - required_attr(state.presence), 152 + required_attr(state.requirement), 153 153 value_attr(state.value), 154 154 disabled_attr(field.disabled), 155 155 aria_describedby_attr(args.described_by), ··· 161 161 162 162 /// Create a `<textarea></textarea>`. 163 163 pub fn textarea_widget() { 164 - fn(field: Field, state: formz.FieldState, args: widget.Args) -> html.Node { 164 + fn(field: Field, state: formz.InputState, args: widget.Args) -> html.Node { 165 165 html.textarea( 166 166 list.flatten([ 167 167 name_attr(field.name), 168 168 id_attr(args.id), 169 - required_attr(state.presence), 169 + required_attr(state.requirement), 170 170 aria_label_attr(args.labelled_by, field.label), 171 171 ]), 172 172 [html.Text(state.value)], ··· 178 178 /// passing data around and you don't want it to be visible to the user. Like 179 179 /// say, the ID of a record being edited. 180 180 pub fn hidden_widget() { 181 - fn(field: Field, state: formz.FieldState, _) -> html.Node { 181 + fn(field: Field, state: formz.InputState, _) -> html.Node { 182 182 html.input( 183 183 list.flatten([ 184 184 type_attr("hidden"), ··· 193 193 /// of variants is a two-tuple, where the first item is the text to display and 194 194 /// the second item is the value. 195 195 pub fn select_widget(variants: List(#(String, String))) { 196 - fn(field: Field, state: formz.FieldState, args: widget.Args) -> html.Node { 196 + fn(field: Field, state: formz.InputState, args: widget.Args) -> html.Node { 197 197 html.select( 198 198 list.flatten([ 199 199 name_attr(field.name), 200 200 id_attr(args.id), 201 - required_attr(state.presence), 201 + required_attr(state.requirement), 202 202 aria_label_attr(args.labelled_by, field.label), 203 203 ]), 204 204 list.flatten([
+2 -2
formz_nakai/test/formz_nakai/widgets_test.gleam
··· 67 67 let string_field = field.Field(name:, label:, help_text:, hidden:, disabled:) 68 68 let field = field.Field(name:, label:, help_text:, hidden:, disabled:) 69 69 70 - let presence = case required { 70 + let requirement = case required { 71 71 True -> formz.Required 72 72 False -> formz.Optional 73 73 } 74 - let state = formz.Valid(value, presence) 74 + let state = formz.Valid(value, requirement) 75 75 76 76 widget(field, state, args) 77 77 |> convert_to_string
+1 -1
formz_string/src/formz_string/simple.gleam
··· 14 14 <> "</div>" 15 15 } 16 16 17 - pub fn generate_item(item: formz.FormItem(widget.Widget)) -> String { 17 + pub fn generate_item(item: formz.Item(widget.Widget)) -> String { 18 18 case item { 19 19 formz.Field(field, state, _) if field.hidden == True -> 20 20 "<input"
+1 -1
formz_string/src/formz_string/widget.gleam
··· 12 12 import formz/field 13 13 14 14 pub type Widget = 15 - fn(field.Field, formz.FieldState, Args) -> String 15 + fn(field.Field, formz.InputState, Args) -> String 16 16 17 17 pub type Args { 18 18 Args(
+19 -19
formz_string/src/formz_string/widgets.gleam
··· 97 97 } 98 98 } 99 99 100 - fn required_attr(presence: formz.FieldPresence) -> String { 101 - case presence { 100 + fn required_attr(requirement: formz.Requirement) -> String { 101 + case requirement { 102 102 formz.Required -> " required" 103 103 formz.Optional -> "" 104 104 } ··· 114 114 /// Create an `<input type="checkbox">`. The checkbox is checked 115 115 /// if the value is "on" (the browser default). 116 116 pub fn checkbox_widget() -> Widget { 117 - fn(field: field.Field, state: formz.FieldState, args: widget.Args) { 117 + fn(field: field.Field, state: formz.InputState, args: widget.Args) { 118 118 let value = state.value 119 119 let state = case state { 120 - formz.Unvalidated(_, presence) -> formz.Unvalidated("", presence) 121 - formz.Valid(_, presence) -> formz.Valid("", presence) 122 - formz.Invalid(_, presence, e) -> formz.Invalid("", presence, e) 120 + formz.Unvalidated(_, requirement) -> formz.Unvalidated("", requirement) 121 + formz.Valid(_, requirement) -> formz.Valid("", requirement) 122 + formz.Invalid(_, requirement, e) -> formz.Invalid("", requirement, e) 123 123 } 124 124 do_input_widget(field, state, args, "checkbox", [checked_attr(value)]) 125 125 } ··· 132 132 /// the step size. If you truly need any float, then a `type="text"` input might be a 133 133 /// better choice. 134 134 pub fn number_widget(step_size: String) -> Widget { 135 - fn(field: field.Field, state: formz.FieldState, args: widget.Args) { 135 + fn(field: field.Field, state: formz.InputState, args: widget.Args) { 136 136 do_input_widget(field, state, args, "number", [step_size_attr(step_size)]) 137 137 } 138 138 } ··· 140 140 /// Create an `<input type="password">`. This will not output the value in the 141 141 /// generated HTML for privacy/security concerns. 142 142 pub fn password_widget() -> Widget { 143 - fn(field: field.Field, state: formz.FieldState, args: widget.Args) { 143 + fn(field: field.Field, state: formz.InputState, args: widget.Args) { 144 144 let state = case state { 145 - formz.Unvalidated(_, presence) -> formz.Unvalidated("", presence) 146 - formz.Valid(_, presence) -> formz.Valid("", presence) 147 - formz.Invalid(_, presence, e) -> formz.Invalid("", presence, e) 145 + formz.Unvalidated(_, requirement) -> formz.Unvalidated("", requirement) 146 + formz.Valid(_, requirement) -> formz.Valid("", requirement) 147 + formz.Invalid(_, requirement, e) -> formz.Invalid("", requirement, e) 148 148 } 149 149 do_input_widget(field, state, args, "password", []) 150 150 } ··· 153 153 /// Generate any `<input>` like `type="text"`, `type="email"` or 154 154 /// `type="url"`. 155 155 pub fn input_widget(type_: String) -> Widget { 156 - fn(field: field.Field, state: formz.FieldState, args: widget.Args) { 156 + fn(field: field.Field, state: formz.InputState, args: widget.Args) { 157 157 do_input_widget(field, state, args, type_, []) 158 158 } 159 159 } 160 160 161 161 fn do_input_widget( 162 162 field: field.Field, 163 - state: formz.FieldState, 163 + state: formz.InputState, 164 164 args: widget.Args, 165 165 type_: String, 166 166 extra_attrs: List(String), ··· 169 169 <> type_attr(type_) 170 170 <> name_attr(field.name) 171 171 <> id_attr(args.id) 172 - <> required_attr(state.presence) 172 + <> required_attr(state.requirement) 173 173 <> disabled_attr(field.disabled) 174 174 <> value_attr(state.value) 175 175 <> aria_label_attr(args.labelled_by, field.label) ··· 180 180 181 181 /// Create a `<textarea></textarea>`. 182 182 pub fn textarea_widget() -> Widget { 183 - fn(field: field.Field, state: formz.FieldState, args: widget.Args) -> String { 183 + fn(field: field.Field, state: formz.InputState, args: widget.Args) -> String { 184 184 // https://chriscoyier.net/2023/09/29/css-solves-auto-expanding-textareas-probably-eventually/ 185 185 // https://til.simonwillison.net/css/resizing-textarea 186 186 "<textarea" 187 187 <> name_attr(field.name) 188 188 <> id_attr(args.id) 189 - <> required_attr(state.presence) 189 + <> required_attr(state.requirement) 190 190 <> disabled_attr(field.disabled) 191 191 <> aria_label_attr(args.labelled_by, field.label) 192 192 <> aria_describedby_attr(args.described_by) ··· 200 200 /// passing data around and you don't want it to be visible to the user. Like 201 201 /// say, the ID of a record being edited. 202 202 pub fn hidden_widget() -> Widget { 203 - fn(field: field.Field, state: formz.FieldState, _args: widget.Args) -> String { 203 + fn(field: field.Field, state: formz.InputState, _args: widget.Args) -> String { 204 204 "<input" 205 205 <> type_attr("hidden") 206 206 <> name_attr(field.name) ··· 213 213 /// of variants is a two-tuple, where the first item is the text to display and 214 214 /// the second item is the value. 215 215 pub fn select_widget(variants: List(#(String, String))) -> Widget { 216 - fn(field: field.Field, state: formz.FieldState, args: widget.Args) { 216 + fn(field: field.Field, state: formz.InputState, args: widget.Args) { 217 217 let choices = 218 218 list.map(variants, fn(variant) { 219 219 let val = variant.1 ··· 231 231 "<select" 232 232 <> name_attr(field.name) 233 233 <> id_attr(args.id) 234 - <> required_attr(state.presence) 234 + <> required_attr(state.requirement) 235 235 <> disabled_attr(field.disabled) 236 236 <> aria_label_attr(args.labelled_by, field.label) 237 237 <> aria_describedby_attr(args.described_by)