this repo has no description
4
fork

Configure Feed

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

clean up api, add sub forms to formz_pipes

rename formz_pipes to formz_builder

+1152 -1152
+10
formz/TODO.md
··· 1 + - form sets 2 + - decoders/toy.decoder? 3 + - csrf token? 4 + - emit warning on duplicate named fields? 5 + - clean names so snake_case? 6 + - disabled fields 7 + - optional fields? (can do optional validation, but that isn't reflected in the html) 8 + - errors on hidden fields? 9 + - custom types for input.hidden, input.disabled, input.required? 10 + - date fields? https://hexdocs.pm/birl/ https://hexdocs.pm/rada/
+16 -8
formz/src/formz/definition.gleam
··· 1 - import formz/field.{type Definition, Definition} 1 + import formz/widget 2 + 3 + pub type Definition(format, output) { 4 + Definition( 5 + widget: widget.Widget(format), 6 + transform: fn(String) -> Result(output, String), 7 + placeholder: output, 8 + ) 9 + } 2 10 3 11 pub fn validates( 4 - definition: Definition(format, output), 12 + kind: Definition(format, output), 5 13 next: fn(output) -> Result(output, String), 6 14 ) -> Definition(format, output) { 7 - let Definition(widget, previous_transform, placeholder) = definition 15 + let Definition(widget, previous_transform, placeholder) = kind 8 16 9 17 Definition( 10 18 widget, ··· 19 27 } 20 28 21 29 pub fn transforms( 22 - definition: Definition(format, a), 30 + kind: Definition(format, a), 23 31 placeholder: b, 24 32 next: fn(a) -> Result(b, String), 25 33 ) -> Definition(format, b) { 26 - let Definition(widget, previous_transform, _) = definition 34 + let Definition(widget, previous_transform, _) = kind 27 35 28 36 Definition( 29 37 widget, ··· 38 46 } 39 47 40 48 pub fn set_widget( 41 - definition: Definition(format, a), 42 - widget: field.Widget(format), 49 + kind: Definition(format, a), 50 + widget: widget.Widget(format), 43 51 ) -> Definition(format, a) { 44 - Definition(..definition, widget:) 52 + Definition(..kind, widget:) 45 53 }
+34 -240
formz/src/formz/field.gleam
··· 1 1 import justin 2 2 3 - pub type Field(format) { 3 + pub type Field { 4 4 Valid( 5 5 name: String, 6 6 label: String, 7 7 help_text: String, 8 8 disabled: Bool, 9 9 required: Bool, 10 - widget: Widget(format), 11 10 hidden: Bool, 12 11 value: String, 13 12 ) ··· 17 16 help_text: String, 18 17 disabled: Bool, 19 18 required: Bool, 20 - widget: Widget(format), 21 19 hidden: Bool, 22 20 value: String, 23 21 error: String, 24 22 ) 25 23 } 26 24 27 - pub type Definition(format, output) { 28 - Definition( 29 - widget: Widget(format), 30 - transform: fn(String) -> Result(output, String), 31 - placeholder: output, 32 - ) 33 - } 34 - 35 - pub opaque type Widget(format) { 36 - Widget(fn(Field(format), WidgetArgs) -> format) 37 - } 38 - 39 - pub type WidgetArgs { 40 - WidgetArgs(id: String, labelled_by: InputLabelled) 41 - } 42 - 43 - pub type InputLabelled { 44 - Element 45 - Value 46 - Id(element_id: String) 47 - } 48 - 49 - pub fn field(named name: String) -> Field(format) { 25 + pub fn field(named name: String) -> Field { 50 26 Valid( 51 27 name: name, 52 28 label: justin.sentence_case(name), 53 29 help_text: "", 54 30 disabled: False, 55 31 required: False, 56 - widget: Widget(fn(_, _) { panic }), 57 32 hidden: False, 58 33 value: "", 59 34 ) 60 35 } 61 36 62 - pub fn widget(fun: fn(Field(format), WidgetArgs) -> format) -> Widget(format) { 63 - Widget(fun) 64 - } 65 - 66 - pub fn set_name(field: Field(format), name: String) -> Field(format) { 37 + pub fn set_name(field: Field, name: String) -> Field { 67 38 case field { 68 - Valid( 69 - _name, 70 - label:, 71 - help_text:, 72 - widget:, 73 - hidden:, 74 - value:, 75 - disabled:, 76 - required:, 77 - ) -> 78 - Valid( 79 - name:, 80 - label:, 81 - help_text:, 82 - widget:, 83 - hidden:, 84 - value:, 85 - disabled:, 86 - required:, 87 - ) 39 + Valid(_name, label:, help_text:, hidden:, value:, disabled:, required:) -> 40 + Valid(name:, label:, help_text:, hidden:, value:, disabled:, required:) 88 41 Invalid( 89 42 _name, 90 43 label:, 91 44 help_text:, 92 - widget:, 93 45 hidden:, 94 46 disabled:, 95 47 required:, ··· 100 52 name:, 101 53 label:, 102 54 help_text:, 103 - widget:, 104 55 hidden:, 105 56 value:, 106 57 error:, ··· 110 61 } 111 62 } 112 63 113 - pub fn set_label(field: Field(format), label: String) -> Field(format) { 64 + pub fn set_label(field: Field, label: String) -> Field { 114 65 case field { 115 - Valid( 116 - _label, 117 - name:, 118 - help_text:, 119 - widget:, 120 - hidden:, 121 - value:, 122 - disabled:, 123 - required:, 124 - ) -> 125 - Valid( 126 - name:, 127 - label:, 128 - help_text:, 129 - widget:, 130 - hidden:, 131 - value:, 132 - disabled:, 133 - required:, 134 - ) 66 + Valid(_label, name:, help_text:, hidden:, value:, disabled:, required:) -> 67 + Valid(name:, label:, help_text:, hidden:, value:, disabled:, required:) 135 68 Invalid( 136 69 _label, 137 70 name:, 138 71 help_text:, 139 - widget:, 140 72 hidden:, 141 73 disabled:, 142 74 required:, ··· 147 79 name:, 148 80 label:, 149 81 help_text:, 150 - widget:, 151 82 hidden:, 152 83 value:, 153 84 error:, ··· 157 88 } 158 89 } 159 90 160 - pub fn set_help_text(field: Field(format), help_text: String) -> Field(format) { 91 + pub fn set_help_text(field: Field, help_text: String) -> Field { 161 92 case field { 162 - Valid( 163 - _help_text, 164 - name:, 165 - label:, 166 - widget:, 167 - hidden:, 168 - value:, 169 - disabled:, 170 - required:, 171 - ) -> 172 - Valid( 173 - name:, 174 - label:, 175 - help_text:, 176 - widget:, 177 - hidden:, 178 - value:, 179 - disabled:, 180 - required:, 181 - ) 93 + Valid(_help_text, name:, label:, hidden:, value:, disabled:, required:) -> 94 + Valid(name:, label:, help_text:, hidden:, value:, disabled:, required:) 182 95 Invalid( 183 96 _help_text, 184 97 name:, 185 98 label:, 186 - widget:, 187 99 hidden:, 188 100 disabled:, 189 101 required:, ··· 194 106 name:, 195 107 label:, 196 108 help_text:, 197 - widget:, 198 109 hidden:, 199 110 value:, 200 111 error:, ··· 204 115 } 205 116 } 206 117 207 - pub fn set_widget(field: Field(format), widget: Widget(format)) -> Field(format) { 208 - case field { 209 - Valid( 210 - _widget, 211 - name:, 212 - label:, 213 - help_text:, 214 - hidden:, 215 - value:, 216 - disabled:, 217 - required:, 218 - ) -> 219 - Valid( 220 - name:, 221 - label:, 222 - help_text:, 223 - widget:, 224 - hidden:, 225 - value:, 226 - disabled:, 227 - required:, 228 - ) 229 - Invalid( 230 - _widget, 231 - name:, 232 - label:, 233 - help_text:, 234 - hidden:, 235 - disabled:, 236 - required:, 237 - value:, 238 - error:, 239 - ) -> 240 - Invalid( 241 - name:, 242 - label:, 243 - help_text:, 244 - widget:, 245 - hidden:, 246 - value:, 247 - error:, 248 - disabled:, 249 - required:, 250 - ) 251 - } 252 - } 253 - 254 - pub fn set_visibility(field: Field(format), hidden: Bool) -> Field(format) { 118 + fn set_hidden(field: Field, hidden: Bool) -> Field { 255 119 case field { 256 - Valid( 257 - _hidden, 258 - name:, 259 - label:, 260 - help_text:, 261 - widget:, 262 - value:, 263 - disabled:, 264 - required:, 265 - ) -> 266 - Valid( 267 - name:, 268 - label:, 269 - help_text:, 270 - widget:, 271 - hidden:, 272 - value:, 273 - disabled:, 274 - required:, 275 - ) 120 + Valid(_hidden, name:, label:, help_text:, value:, disabled:, required:) -> 121 + Valid(name:, label:, help_text:, hidden:, value:, disabled:, required:) 276 122 Invalid( 277 123 _hidden, 278 124 name:, 279 125 label:, 280 126 help_text:, 281 - widget:, 282 127 disabled:, 283 128 required:, 284 129 value:, ··· 288 133 name:, 289 134 label:, 290 135 help_text:, 291 - widget:, 292 136 hidden:, 293 137 value:, 294 138 error:, ··· 298 142 } 299 143 } 300 144 301 - pub fn make_hidden(field: Field(format)) -> Field(format) { 302 - set_visibility(field, True) 145 + pub fn make_hidden(field: Field) -> Field { 146 + set_hidden(field, True) 303 147 } 304 148 305 - pub fn make_visible(field: Field(format)) -> Field(format) { 306 - set_visibility(field, False) 149 + pub fn make_visible(field: Field) -> Field { 150 + set_hidden(field, False) 307 151 } 308 152 309 - pub fn set_disabled(field: Field(format), disabled: Bool) -> Field(format) { 153 + fn set_disabled(field: Field, disabled: Bool) -> Field { 310 154 case field { 311 - Valid( 312 - _disabled, 313 - name:, 314 - label:, 315 - help_text:, 316 - widget:, 317 - value:, 318 - hidden:, 319 - required:, 320 - ) -> 321 - Valid( 322 - name:, 323 - label:, 324 - help_text:, 325 - widget:, 326 - hidden:, 327 - value:, 328 - disabled:, 329 - required:, 330 - ) 155 + Valid(_disabled, name:, label:, help_text:, value:, hidden:, required:) -> 156 + Valid(name:, label:, help_text:, hidden:, value:, disabled:, required:) 331 157 Invalid( 332 158 _disabled, 333 159 name:, 334 160 label:, 335 161 help_text:, 336 - widget:, 337 162 hidden:, 338 163 required:, 339 164 value:, ··· 343 168 name:, 344 169 label:, 345 170 help_text:, 346 - widget:, 347 171 hidden:, 348 172 value:, 349 173 error:, ··· 353 177 } 354 178 } 355 179 356 - pub fn set_value(field: Field(format), value: String) -> Field(format) { 180 + pub fn make_disabled(field: Field) -> Field { 181 + set_disabled(field, True) 182 + } 183 + 184 + pub fn make_enabled(field: Field) -> Field { 185 + set_disabled(field, False) 186 + } 187 + 188 + pub fn set_string_value(field: Field, value: String) -> Field { 357 189 case field { 358 - Valid( 359 - _value, 360 - name:, 361 - label:, 362 - help_text:, 363 - widget:, 364 - hidden:, 365 - disabled:, 366 - required:, 367 - ) -> 368 - Valid( 369 - name:, 370 - label:, 371 - help_text:, 372 - widget:, 373 - hidden:, 374 - value:, 375 - disabled:, 376 - required:, 377 - ) 190 + Valid(_value, name:, label:, help_text:, hidden:, disabled:, required:) -> 191 + Valid(name:, label:, help_text:, hidden:, value:, disabled:, required:) 378 192 Invalid( 379 193 _value, 380 194 name:, 381 195 label:, 382 196 help_text:, 383 - widget:, 384 197 hidden:, 385 198 disabled:, 386 199 required:, ··· 390 203 name:, 391 204 label:, 392 205 help_text:, 393 - widget:, 394 206 hidden:, 395 207 value:, 396 208 error:, ··· 400 212 } 401 213 } 402 214 403 - pub fn set_error(field: Field(format), error: String) -> Field(format) { 215 + pub fn set_error(field: Field, error: String) -> Field { 404 216 case field { 405 - Valid( 406 - name:, 407 - label:, 408 - help_text:, 409 - widget:, 410 - hidden:, 411 - value:, 412 - disabled:, 413 - required:, 414 - ) -> 217 + Valid(name:, label:, help_text:, hidden:, value:, disabled:, required:) -> 415 218 Invalid( 416 219 name:, 417 220 label:, 418 221 help_text:, 419 - widget:, 420 222 hidden:, 421 223 value:, 422 224 disabled:, ··· 428 230 name:, 429 231 label:, 430 232 help_text:, 431 - widget:, 432 233 hidden:, 433 234 disabled:, 434 235 required:, ··· 438 239 name:, 439 240 label:, 440 241 help_text:, 441 - widget:, 442 242 hidden:, 443 243 value:, 444 244 error:, ··· 447 247 ) 448 248 } 449 249 } 450 - 451 - pub fn run_widget(f: Field(format), args: WidgetArgs) -> format { 452 - let Widget(fun) = f.widget 453 - 454 - fun(f, args) 455 - }
+282
formz/src/formz/formz_builder.gleam
··· 1 + import formz/definition.{type Definition} 2 + import formz/field 3 + import formz/subform 4 + import formz/widget 5 + 6 + import gleam/dict 7 + import gleam/list 8 + import gleam/option.{type Option, None, Some} 9 + import gleam/result 10 + 11 + pub type HasDecoder 12 + 13 + pub type NoDecoder 14 + 15 + pub opaque type Form(format, output, decoder, has_decoder) { 16 + Form( 17 + items: List(FormItem(format)), 18 + parse_with: fn(List(FormItem(format)), decoder) -> 19 + Result(output, List(FormItem(format))), 20 + decoder: Option(decoder), 21 + ) 22 + } 23 + 24 + pub type FormItem(format) { 25 + Element(field.Field, widget: widget.Widget(format)) 26 + Set(subform.SubForm, items: List(FormItem(format))) 27 + } 28 + 29 + pub fn new() -> Form(format, a, a, NoDecoder) { 30 + Form([], fn(_, output) { Ok(output) }, None) 31 + } 32 + 33 + pub fn add( 34 + previous_form: Form( 35 + format, 36 + fn(decoder_step_input) -> decoder_step_output, 37 + form_output, 38 + has_decoder, 39 + ), 40 + field: field.Field, 41 + definition: Definition(format, decoder_step_input), 42 + ) -> Form(format, decoder_step_output, form_output, has_decoder) { 43 + let updated_items = [Element(field, definition.widget), ..previous_form.items] 44 + 45 + let parse_with = fn(items, decoder: form_output) { 46 + // can do let assert because we know there's at least one field since 47 + // we just added one 48 + let assert Ok(#(Element(field, widget), rest)) = pop_element(items) 49 + 50 + let input_output = definition.transform(field.value) 51 + 52 + let previous_form_output = previous_form.parse_with(rest, decoder) 53 + case previous_form_output, input_output { 54 + // the form we've already parsed has no errors and the field 55 + // we just parsed has no errors. so we can move on to the next 56 + Ok(next), Ok(value) -> Ok(next(value)) 57 + 58 + // the form already has errors even though this one succeeded. 59 + // so add this to the list and stop anymore parsing 60 + Error(items), Ok(_value) -> Error([Element(field, widget), ..items]) 61 + 62 + // form was good so far, but this field errored, so need to 63 + // mark this field as invalid and return all the fields we've got 64 + // so far 65 + Ok(_), Error(error) -> 66 + field 67 + |> field.set_error(error) 68 + |> Element(widget) 69 + |> list.prepend(rest, _) 70 + |> Error 71 + 72 + // form already has errors and this field errored, so add this field 73 + // to the list 74 + Error(items), Error(error) -> 75 + field 76 + |> field.set_error(error) 77 + |> Element(widget) 78 + |> list.prepend(items, _) 79 + |> Error 80 + } 81 + } 82 + 83 + Form(items: updated_items, parse_with:, decoder: previous_form.decoder) 84 + } 85 + 86 + pub fn add_form( 87 + previous_form: Form( 88 + format, 89 + fn(sub_output) -> decoder_step_output, 90 + form_output, 91 + has_decoder, 92 + ), 93 + subform: subform.SubForm, 94 + sub: Form(format, sub_output, sub_decoder, HasDecoder), 95 + ) -> Form(format, decoder_step_output, form_output, has_decoder) { 96 + let sub_items = 97 + sub.items 98 + |> map_fields(fn(field) { 99 + field |> field.set_name(subform.name <> "." <> field.name) 100 + }) 101 + 102 + let updated_items = [Set(subform, sub_items), ..previous_form.items] 103 + 104 + let parse_with = fn(items, decoder: form_output) { 105 + // can do let assert because we know there's at least one field since 106 + // we just added one 107 + let assert Ok(#(Set(subform, sub_items), rest)) = pop_element(items) 108 + 109 + let assert Form(_, sub_parse_with, Some(sub_decoder)) = sub 110 + let form_output = sub_parse_with(sub_items, sub_decoder) 111 + 112 + let previous_form_output = previous_form.parse_with(rest, decoder) 113 + case previous_form_output, form_output { 114 + // everything is good! pass along the output 115 + Ok(next), Ok(value) -> Ok(next(value)) 116 + 117 + // form has errors, but this sub form was good, so add it to the list 118 + // of items as is. 119 + Error(items), Ok(_value) -> Error([Set(subform, items), ..items]) 120 + 121 + // form was good so far, but this sub form errored, so need to 122 + // hop on error track 123 + Ok(_), Error(error_fields) -> Error([Set(subform, error_fields), ..rest]) 124 + 125 + // form already has errors and this form errored, so add this field 126 + // to the list of errors 127 + Error(fields), Error(error_fields) -> 128 + Error(list.prepend(fields, Set(subform, error_fields))) 129 + } 130 + } 131 + Form(items: updated_items, parse_with:, decoder: previous_form.decoder) 132 + } 133 + 134 + fn pop_element( 135 + items: List(FormItem(format)), 136 + ) -> Result(#(FormItem(format), List(FormItem(format))), Nil) { 137 + case items { 138 + [] -> Error(Nil) 139 + [only] -> Ok(#(only, [])) 140 + [Element(..) as item, ..rest] -> Ok(#(item, rest)) 141 + [Set(_, []), ..rest] -> pop_element(rest) 142 + [Set(s, [first, ..rest_1]), ..rest_2] -> 143 + pop_element(list.flatten([[first], [Set(s, rest_1)], rest_2])) 144 + } 145 + } 146 + 147 + fn map_fields( 148 + items: List(FormItem(format)), 149 + fun: fn(field.Field) -> field.Field, 150 + ) -> List(FormItem(format)) { 151 + list.map(items, fn(item) { 152 + case item { 153 + Element(field, widget) -> Element(fun(field), widget) 154 + Set(s, items) -> Set(s, map_fields(items, fun)) 155 + } 156 + }) 157 + } 158 + 159 + pub fn data( 160 + form: Form(format, output, decoder, has_decoder), 161 + input_data: List(#(String, String)), 162 + ) -> Form(format, output, decoder, has_decoder) { 163 + let data = dict.from_list(input_data) 164 + let Form(items, parse, placeholder) = form 165 + items 166 + |> map_fields(fn(field) { 167 + case dict.get(data, field.name) { 168 + Ok(value) -> field.set_string_value(field, value) 169 + Error(_) -> field 170 + } 171 + }) 172 + |> Form(parse, placeholder) 173 + } 174 + 175 + pub fn decodes( 176 + form: Form(format, output, decoder, has_decoder), 177 + decoder: decoder, 178 + ) -> Form(format, output, decoder, HasDecoder) { 179 + let Form(fields, parse_with, _) = form 180 + Form(fields, parse_with, Some(decoder)) 181 + } 182 + 183 + // pub fn remove_decoder( 184 + // form: Form(format, output1, decoder2, has_decoder), 185 + // ) -> Form(format, decoder2, decoder2, NoDecoder) { 186 + // let Form(fields, parse_with, _) = form 187 + // let parse_with = fn(items: List(FormItem(format)), decoder) { 188 + // parse_with(items, decoder) 189 + // } 190 + // Form(fields, parse_with, None) 191 + // } 192 + 193 + pub fn parse( 194 + form: Form(format, output, decoder, HasDecoder), 195 + ) -> Result(output, Form(format, output, decoder, HasDecoder)) { 196 + // we've tagged that we have a decoder with our has_decoder phantom type 197 + // so we can get away with let assert here 198 + let assert Form(items, parse_with, Some(decoder)) = form 199 + case parse_with(items, decoder) { 200 + Ok(output) -> Ok(output) 201 + Error(items) -> Error(Form(items, parse_with, Some(decoder))) 202 + } 203 + } 204 + 205 + pub fn parse_try( 206 + form: Form(format, output, decoder, HasDecoder), 207 + apply fun: fn(output, Form(format, output, decoder, HasDecoder)) -> 208 + Result(c, Form(format, output, decoder, HasDecoder)), 209 + ) -> Result(c, Form(format, output, decoder, HasDecoder)) { 210 + parse(form) |> result.try(fun(_, form)) 211 + } 212 + 213 + pub fn items(form: Form(format, a, b, has_decoder)) -> List(FormItem(format)) { 214 + form.items |> list.reverse 215 + } 216 + 217 + pub fn get( 218 + form: Form(format, output, decoder, has_decoder), 219 + name: String, 220 + ) -> Result(FormItem(format), Nil) { 221 + form.items 222 + |> list.filter(fn(item) { 223 + case item { 224 + Element(i, _) if i.name == name -> True 225 + Set(s, _) if s.name == name -> True 226 + _ -> False 227 + } 228 + }) 229 + |> list.first 230 + } 231 + 232 + pub fn update( 233 + form: Form(format, output, decoder, has_decoder), 234 + name: String, 235 + fun: fn(FormItem(format)) -> FormItem(format), 236 + ) { 237 + form.items 238 + |> do_formitem_update(name, fun) 239 + |> Form(form.parse_with, form.decoder) 240 + } 241 + 242 + fn do_formitem_update( 243 + items: List(FormItem(format)), 244 + name: String, 245 + fun: fn(FormItem(format)) -> FormItem(format), 246 + ) -> List(FormItem(format)) { 247 + items 248 + |> list.map(fn(item) { 249 + case item { 250 + Element(i, _) if i.name == name -> fun(item) 251 + Set(s, _) if s.name == name -> fun(item) 252 + Set(s, items) -> Set(s, do_formitem_update(items, name, fun)) 253 + _ -> item 254 + } 255 + }) 256 + } 257 + 258 + pub fn update_field( 259 + form: Form(format, output, decoder, has_decoder), 260 + name: String, 261 + fun: fn(field.Field) -> field.Field, 262 + ) -> Form(format, output, decoder, has_decoder) { 263 + update(form, name, fn(item) { 264 + case item { 265 + Element(field, widget) -> Element(fun(field), widget) 266 + _ -> item 267 + } 268 + }) 269 + } 270 + 271 + pub fn update_fieldset( 272 + form: Form(format, output, decoder, has_decoder), 273 + name: String, 274 + fun: fn(subform.SubForm) -> subform.SubForm, 275 + ) -> Form(format, output, decoder, has_decoder) { 276 + update(form, name, fn(item) { 277 + case item { 278 + Set(subform, items) -> Set(fun(subform), items) 279 + _ -> item 280 + } 281 + }) 282 + }
-173
formz/src/formz/formz_pipes.gleam
··· 1 - // https://docs.djangoproject.com/en/5.0/topics/forms/ 2 - // https://github.com/nakaixo/nakai 3 - // date time handling https://hexdocs.pm/birl/index.html 4 - 5 - // TODO 6 - // - form sets 7 - // - decoders/toy.decoder? 8 - // - csrf token 9 - // - emit warning on duplicate named fields 10 - // - clean names so snake_case? 11 - // - disabled fields 12 - // - optional fields? (can do optional validation, but that isn't reflected in the html) 13 - // - errors on hidden fields? 14 - // - custom types for input.hidden, input.disabled, input.required? 15 - 16 - // import formz/field.{type Field} 17 - // import formz/input.{type Input, Valid} 18 - // import gleam/list 19 - // import gleam/option.{type Option, None, Some} 20 - // import gleam/result 21 - 22 - // pub type HasDecoder 23 - 24 - // pub type NoDecoder 25 - 26 - // pub opaque type Form(format, output, decoder, has_decoder) { 27 - // Form( 28 - // fields: List(Input(format)), 29 - // parse_with: fn(List(Input(format)), decoder) -> 30 - // Result(output, List(Input(format))), 31 - // decoder: Option(decoder), 32 - // ) 33 - // } 34 - 35 - // pub fn new() -> Form(format, a, a, NoDecoder) { 36 - // Form([], fn(_, output) { Ok(output) }, None) 37 - // } 38 - 39 - // pub fn add( 40 - // form: Form( 41 - // format, 42 - // fn(decoder_step_input) -> decoder_step_output, 43 - // form_output, 44 - // has_decoder, 45 - // ), 46 - // definition: Field(format, decoder_step_input), 47 - // ) -> Form(format, decoder_step_output, form_output, has_decoder) { 48 - // let Form(inputs, parse_with, decoder) = form 49 - 50 - // // create new form with the new field and update the parse 51 - // // function to handle the new details from the type of the 52 - // // field 53 - // Form( 54 - // fields: [definition.input, ..inputs], 55 - // parse_with: fn(inputs, decoder: form_output) { 56 - // // can do let assert because we know there's at least one field since 57 - // // we just added one 58 - // let assert [input, ..rest] = inputs 59 - // case parse_with(rest, decoder), definition.transform(input.value) { 60 - // // the form we've already parsed has no errors and the field 61 - // // we just parsed has no errors. so we can move on to the next 62 - // Ok(next), Ok(value) -> Ok(next(value)) 63 - 64 - // // the form already has errors even though this one succeeded. 65 - // // so add this to the list and stop anymore parsing 66 - // Error(inputs), Ok(_value) -> Error([input, ..inputs]) 67 - 68 - // // form was good so far, but this field errored, so need to 69 - // // mark this field as invalid and return all the fields we've got 70 - // // so far 71 - // Ok(_), Error(error) -> Error([input.set_error(input, error), ..rest]) 72 - 73 - // // form already has errors and this field errored, so add this field 74 - // // to the list 75 - // Error(fields), Error(error) -> 76 - // Error([input.set_error(input, error), ..fields]) 77 - // } 78 - // }, 79 - // decoder:, 80 - // ) 81 - // } 82 - 83 - // pub fn data( 84 - // form: Form(format, a, b, has_decoder), 85 - // field: List(#(String, String)), 86 - // ) -> Form(format, a, b, has_decoder) { 87 - // case form { 88 - // Form(fields, parse_with, decoder) -> { 89 - // fields 90 - // // we always prepend fields, so reverse to get correct order 91 - // // TODO I think we're going to make it so order doesn't matter 92 - // |> list.reverse 93 - // |> do_add_input_data(field, []) 94 - // |> Form(parse_with, decoder) 95 - // } 96 - // // FormWithErrors(..) -> form 97 - // } 98 - // } 99 - 100 - // fn do_add_input_data( 101 - // fields: List(Input(format)), 102 - // data: List(#(String, String)), 103 - // acc: List(Input(format)), 104 - // ) { 105 - // case fields, data { 106 - // // no more fields, we've return all the fields with data we have accumulated 107 - // [], _ -> acc 108 - // // no more data! return all the fields we have left plus the ones we accumulated 109 - // _, [] -> list.append(fields, acc) 110 - // // we have a field and data, and the names match. update field to have data 111 - // [Valid(name: field_name, ..) as field, ..fields_rest], 112 - // [#(data_name, value), ..data_rest] 113 - // if field_name == data_name 114 - // -> 115 - // do_add_input_data(fields_rest, data_rest, [ 116 - // input.set_value(field, value), 117 - // ..acc 118 - // ]) 119 - // // at this point we still have fields and data left, but the first 120 - // // field and first data don't match. so we decide we've got no data 121 - // // for the first field and move on to the next. but we need to add 122 - // // this field without data to the accumulator 123 - // [field, ..fields_rest], _ -> 124 - // do_add_input_data(fields_rest, data, [field, ..acc]) 125 - // } 126 - // } 127 - 128 - // pub fn decodes( 129 - // form: Form(format, output, decoder, has_decoder), 130 - // decoder: decoder, 131 - // ) -> Form(format, output, decoder, HasDecoder) { 132 - // let Form(fields, parse_with, _) = form 133 - // Form(fields, parse_with, Some(decoder)) 134 - // } 135 - 136 - // pub fn parse( 137 - // form: Form(format, output, decoder, HasDecoder), 138 - // ) -> Result(output, Form(format, output, decoder, HasDecoder)) { 139 - // // we've tagged that we have a decoder with out has_decoder phantom type 140 - // // so we can get away with let assert here 141 - // let assert Form(fields, parse_with, Some(decoder)) = form 142 - // case parse_with(fields, decoder) { 143 - // Ok(output) -> Ok(output) 144 - // Error(fields) -> Error(Form(fields, parse_with, Some(decoder))) 145 - // } 146 - // } 147 - 148 - // pub fn try( 149 - // form: Form(format, output, decoder, HasDecoder), 150 - // apply fun: fn(output, Form(format, output, decoder, HasDecoder)) -> 151 - // Result(c, Form(format, output, decoder, HasDecoder)), 152 - // ) -> Result(c, Form(format, output, decoder, HasDecoder)) { 153 - // parse(form) |> result.try(fun(_, form)) 154 - // } 155 - 156 - // pub fn get_inputs(form: Form(format, a, b, has_decoder)) -> List(Input(format)) { 157 - // form.fields |> list.reverse 158 - // } 159 - 160 - // pub fn update_input( 161 - // form: Form(format, output, decoder, has_decoder), 162 - // name: String, 163 - // fun: fn(Input(format)) -> Input(format), 164 - // ) -> Form(format, output, decoder, has_decoder) { 165 - // form.fields 166 - // |> list.map(fn(field) { 167 - // case field.name == name { 168 - // True -> fun(field) 169 - // False -> field 170 - // } 171 - // }) 172 - // |> Form(form.parse_with, form.decoder) 173 - // }
+94 -104
formz/src/formz/formz_use.gleam
··· 1 - import formz/field.{type Definition, type Field} 1 + import formz/definition.{type Definition} 2 + import formz/field 3 + import formz/subform 4 + import formz/widget 2 5 import gleam/dict 3 6 import gleam/list 4 7 import gleam/result ··· 12 15 } 13 16 14 17 pub type FormItem(format) { 15 - Item(input: Field(format)) 16 - Set(Fieldset(format), items: List(FormItem(format))) 17 - } 18 - 19 - pub type Fieldset(format) { 20 - Fieldset(prefix: String, label: String) 18 + Element(field.Field, widget: widget.Widget(format)) 19 + Set(subform.SubForm, items: List(FormItem(format))) 21 20 } 22 21 23 22 pub fn create_form(thing: thing) -> Form(format, thing) { ··· 25 24 } 26 25 27 26 pub fn with( 28 - field: Field(format), 29 - definition: Definition(format, input_output), 30 - fun: fn(input_output) -> Form(format, form_output), 27 + field: field.Field, 28 + is definition: Definition(format, input_output), 29 + rest fun: fn(input_output) -> Form(format, form_output), 31 30 ) -> Form(format, form_output) { 32 31 // we pass in our placeholder value, and we're going to throw away the 33 32 // decoded result here, we just care about pulling out the fields 34 33 // from the form. 35 - let next = fun(definition.placeholder) 36 - 37 - let field = field |> field.set_widget(definition.widget) 34 + let next_form = fun(definition.placeholder) 38 35 39 - // prepend the new input to the inputs from the form we got in the 36 + // prepend the new field to the items from the form we got in the 40 37 // previous step. 41 - let updated_inputs = [Item(field), ..next.items] 38 + let updated_items = [Element(field, definition.widget), ..next_form.items] 42 39 43 40 // now create the parse function. parse function accepts most recent 44 41 // version of input list, since data can be added to it. the list 45 42 // above we just needed for the initial setup. 46 - let parse = fn(inputs: List(FormItem(format))) { 43 + let parse = fn(items: List(FormItem(format))) { 47 44 // pull out the latest version of this field to get latest input data 48 - let assert Ok(#(Item(input), next_inputs)) = next_item(inputs) 45 + let assert Ok(#(Element(field, widget), pop_elements)) = pop_element(items) 49 46 50 47 // transform the input data using the transform/validate/decode/etc function 51 - let input_output = definition.transform(input.value) 48 + let input_output = definition.transform(field.value) 52 49 53 50 // pass our transformed input data to the next function/form. if 54 51 // we errored we still do this with our placeholder so we can continue 55 - // processing all the fields in the form. we will return a form 56 - // with an error, so if we're on the error track we'll throw away 57 - // the "output" made with this and just keep the errors. 52 + // processing all the fields in the form. if we're on the error track 53 + // we'll throw away the "output" made with this and just keep the error 58 54 let next_form = fun(input_output |> result.unwrap(definition.placeholder)) 59 - let form_output = next_form.parse(next_inputs) 55 + let form_output = next_form.parse(pop_elements) 60 56 61 57 // ok, check which track we're on 62 58 case form_output, input_output { ··· 65 61 66 62 // form has errors, but this field was good, so add it to the list 67 63 // of fields as is. 68 - Error(inputs), Ok(_value) -> Error([Item(input), ..inputs]) 64 + Error(items), Ok(_value) -> Error([Element(field, widget), ..items]) 69 65 70 66 // form was good so far, but this field errored, so need to 71 67 // mark this field as invalid and return all the fields we've got 72 68 // so far 73 69 Ok(_), Error(error) -> 74 - input 70 + field 75 71 |> field.set_error(error) 76 - |> Item 77 - |> list.prepend(next_inputs, _) 72 + |> Element(widget) 73 + |> list.prepend(pop_elements, _) 78 74 |> Error 79 75 80 76 // form already has errors and this field errored, so add this field 81 77 // to the list of errors 82 - Error(fields), Error(error) -> 83 - input 78 + Error(items), Error(error) -> 79 + field 84 80 |> field.set_error(error) 85 - |> Item 86 - |> list.prepend(fields, _) 81 + |> Element(widget) 82 + |> list.prepend(items, _) 87 83 |> Error 88 84 } 89 85 } 90 - Form(updated_inputs, parse: parse, placeholder: next.placeholder) 86 + Form(items: updated_items, parse:, placeholder: next_form.placeholder) 91 87 } 92 88 93 - pub fn sub_form( 94 - prefix: String, 95 - name: String, 89 + pub fn with_form( 90 + subform: subform.SubForm, 96 91 sub: Form(format, sub_output), 97 92 fun: fn(sub_output) -> Form(format, form_output), 98 93 ) -> Form(format, form_output) { 99 - let next = fun(sub.placeholder) 94 + let next_form = fun(sub.placeholder) 100 95 101 - let sub_inputs = 96 + let sub_items = 102 97 sub.items 103 - |> map_inputs(fn(input) { 104 - input |> field.set_name(prefix <> "." <> input.name) 98 + |> map_fields(fn(field) { 99 + field |> field.set_name(subform.name <> "." <> field.name) 105 100 }) 106 101 107 - let updated_inputs = [Set(Fieldset(prefix, name), sub_inputs), ..next.items] 102 + let updated_items = [Set(subform, sub_items), ..next_form.items] 108 103 109 - let parse = fn(inputs: List(FormItem(format))) { 104 + let parse = fn(items: List(FormItem(format))) { 110 105 // pull out the latest version of this field to get latest input data 111 - let assert [Set(Fieldset(_, name), items), ..next_inputs] = inputs 106 + let assert [Set(subform, sub_items), ..next_items] = items 112 107 113 - let sub_output = sub.parse(items) 108 + let sub_output = sub.parse(sub_items) 114 109 115 110 let next_form = fun(sub_output |> result.unwrap(sub.placeholder)) 116 - let form_output = next_form.parse(next_inputs) 111 + let form_output = next_form.parse(next_items) 117 112 118 113 // ok, check which track we're on 119 114 case form_output, sub_output { ··· 122 117 123 118 // form has errors, but this sub form was good, so add it to the list 124 119 // of items as is. 125 - Error(inputs), Ok(_value) -> 126 - Error([Set(Fieldset(prefix, name), items), ..inputs]) 120 + Error(items), Ok(_value) -> Error([Set(subform, items), ..items]) 127 121 128 122 // form was good so far, but this sub form errored, so need to 129 123 // hop on error track 130 124 Ok(_), Error(error_fields) -> 131 - Error([Set(Fieldset(prefix, name), error_fields), ..next_inputs]) 125 + Error([Set(subform, error_fields), ..next_items]) 132 126 133 127 // form already has errors and this form errored, so add this field 134 128 // to the list of errors 135 129 Error(fields), Error(error_fields) -> 136 - Error(list.prepend(fields, Set(Fieldset(prefix, name), error_fields))) 130 + Error(list.prepend(fields, Set(subform, error_fields))) 137 131 } 138 132 } 139 - Form(updated_inputs, parse: parse, placeholder: next.placeholder) 133 + Form(updated_items, parse: parse, placeholder: next_form.placeholder) 140 134 } 141 135 142 - fn next_item( 136 + fn pop_element( 143 137 items: List(FormItem(format)), 144 138 ) -> Result(#(FormItem(format), List(FormItem(format))), Nil) { 145 139 case items { 146 140 [] -> Error(Nil) 147 141 [only] -> Ok(#(only, [])) 148 - [Item(input), ..rest] -> Ok(#(Item(input), rest)) 149 - [Set(_, []), ..rest] -> next_item(rest) 142 + [Element(..) as item, ..rest] -> Ok(#(item, rest)) 143 + [Set(_, []), ..rest] -> pop_element(rest) 150 144 [Set(s, [first, ..rest_1]), ..rest_2] -> 151 - next_item(list.flatten([[first], [Set(s, rest_1)], rest_2])) 145 + pop_element(list.flatten([[first], [Set(s, rest_1)], rest_2])) 152 146 } 153 147 } 154 148 155 - fn map_inputs( 149 + fn map_fields( 156 150 items: List(FormItem(format)), 157 - fun: fn(Field(format)) -> Field(format), 151 + fun: fn(field.Field) -> field.Field, 158 152 ) -> List(FormItem(format)) { 159 153 list.map(items, fn(item) { 160 154 case item { 161 - Item(input) -> Item(fun(input)) 162 - Set(s, items) -> Set(s, map_inputs(items, fun)) 155 + Element(field, widget) -> Element(fun(field), widget) 156 + Set(s, items) -> Set(s, map_fields(items, fun)) 163 157 } 164 158 }) 165 159 } 166 160 167 - fn get_inputs(form: Form(format, ouput)) { 168 - form.items |> do_get_inputs([]) |> list.reverse 169 - } 170 - 171 - fn do_get_inputs(items: List(FormItem(format)), acc) { 172 - case items { 173 - [] -> acc 174 - [Item(input), ..rest] -> do_get_inputs(rest, [input, ..acc]) 175 - [Set(_, items), ..rest] -> do_get_inputs(list.flatten([items, rest]), acc) 176 - } 177 - } 178 - 179 161 pub fn data( 180 162 form: Form(format, output), 181 163 input_data: List(#(String, String)), ··· 183 165 let data = dict.from_list(input_data) 184 166 let Form(items, parse, placeholder) = form 185 167 items 186 - |> map_inputs(fn(field) { 168 + |> map_fields(fn(field) { 187 169 case dict.get(data, field.name) { 188 - Ok(value) -> field.set_value(field, value) 170 + Ok(value) -> field.set_string_value(field, value) 189 171 Error(_) -> field 190 172 } 191 173 }) ··· 193 175 } 194 176 195 177 pub fn parse(form: Form(format, output)) -> Result(output, Form(format, output)) { 196 - // we've tagged that we have a decoder with out has_decoder phantom type 197 - // so we can get away with let assert here 198 - let Form(fields, parse, placeholder) = form 199 - case parse(fields) { 178 + case form.parse(form.items) { 200 179 Ok(output) -> Ok(output) 201 - Error(fields) -> Error(Form(fields, parse, placeholder)) 180 + Error(items) -> Error(Form(..form, items:)) 202 181 } 203 182 } 204 183 205 - pub fn try( 184 + pub fn parse_try( 206 185 form: Form(format, output), 207 186 apply fun: fn(output, Form(format, output)) -> Result(c, Form(format, output)), 208 187 ) -> Result(c, Form(format, output)) { 209 188 parse(form) |> result.try(fun(_, form)) 210 189 } 211 190 212 - pub fn get_items(form: Form(format, output)) -> List(FormItem(format)) { 191 + pub fn items(form: Form(format, output)) -> List(FormItem(format)) { 213 192 form.items 214 193 } 215 194 216 - pub fn get_item( 195 + pub fn get( 217 196 form: Form(format, output), 218 - prefix: String, 197 + name: String, 219 198 ) -> Result(FormItem(format), Nil) { 220 199 form.items 221 200 |> list.filter(fn(item) { 222 201 case item { 223 - Item(i) if i.name == prefix -> True 224 - Set(s, _) if s.prefix == prefix -> True 202 + Element(i, _) if i.name == name -> True 203 + Set(s, _) if s.name == name -> True 225 204 _ -> False 226 205 } 227 206 }) 228 207 |> list.first 229 208 } 230 209 231 - pub fn map_item( 210 + pub fn update( 232 211 form: Form(format, output), 233 212 name: String, 234 - fun: fn(FormItem(format)) -> a, 235 - ) -> Result(a, Nil) { 236 - form |> get_item(name) |> result.map(fun) 213 + fun: fn(FormItem(format)) -> FormItem(format), 214 + ) { 215 + form.items 216 + |> do_formitem_update(name, fun) 217 + |> Form(form.parse, form.placeholder) 237 218 } 238 219 239 - pub fn get_input( 240 - form: Form(format, output), 220 + fn do_formitem_update( 221 + items: List(FormItem(format)), 241 222 name: String, 242 - ) -> Result(Field(format), Nil) { 243 - form 244 - |> get_inputs 245 - |> list.filter(fn(input) { input.name == name }) 246 - |> list.first 223 + fun: fn(FormItem(format)) -> FormItem(format), 224 + ) -> List(FormItem(format)) { 225 + items 226 + |> list.map(fn(item) { 227 + case item { 228 + Element(i, _) if i.name == name -> fun(item) 229 + Set(s, _) if s.name == name -> fun(item) 230 + Set(s, items) -> Set(s, do_formitem_update(items, name, fun)) 231 + _ -> item 232 + } 233 + }) 247 234 } 248 235 249 - pub fn map_input( 236 + pub fn update_field( 250 237 form: Form(format, output), 251 238 name: String, 252 - fun: fn(Field(format)) -> a, 253 - ) -> Result(a, Nil) { 254 - form |> get_input(name) |> result.map(fun) 239 + fun: fn(field.Field) -> field.Field, 240 + ) -> Form(format, output) { 241 + update(form, name, fn(item) { 242 + case item { 243 + Element(field, widget) -> Element(fun(field), widget) 244 + _ -> item 245 + } 246 + }) 255 247 } 256 248 257 - pub fn update_input( 249 + pub fn update_fieldset( 258 250 form: Form(format, output), 259 251 name: String, 260 - fun: fn(Field(format)) -> Field(format), 252 + fun: fn(subform.SubForm) -> subform.SubForm, 261 253 ) -> Form(format, output) { 262 - form.items 263 - |> map_inputs(fn(field) { 264 - case field.name == name { 265 - True -> fun(field) 266 - False -> field 254 + update(form, name, fn(item) { 255 + case item { 256 + Set(subform, items) -> Set(fun(subform), items) 257 + _ -> item 267 258 } 268 259 }) 269 - |> Form(form.parse, form.placeholder) 270 260 }
+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 + }
+14
formz/src/formz/widget.gleam
··· 1 + import formz/field 2 + 3 + pub type Widget(format) = 4 + fn(field.Field, Args) -> format 5 + 6 + pub type Args { 7 + Args(id: String, labelled_by: LabelledBy) 8 + } 9 + 10 + pub type LabelledBy { 11 + LabelledByLabelFor 12 + LabelledByFieldValue 13 + LabelledByElementWithId(id: String) 14 + }
+344
formz/test/formz/formz_builder_test.gleam
··· 1 + import formz/definition 2 + import formz/field.{field} 3 + import formz/formz_builder.{Element, Set} as formz 4 + import formz/subform.{subform} 5 + import formz/validation 6 + import gleam/list 7 + import gleeunit 8 + import gleeunit/should 9 + 10 + pub fn main() { 11 + gleeunit.main() 12 + } 13 + 14 + pub fn text_field() { 15 + definition.Definition(fn(_, _) { "" }, validation.string, "") 16 + } 17 + 18 + pub fn integer_field() { 19 + definition.Definition(fn(_, _) { "" }, validation.int, 0) 20 + } 21 + 22 + pub fn float_field() { 23 + definition.Definition(fn(_, _) { "" }, validation.number, 0.0) 24 + } 25 + 26 + fn get_form_from_error_result( 27 + result: Result(output, formz.Form(format, output, decoder, has_decoder)), 28 + ) -> formz.Form(format, output, decoder, has_decoder) { 29 + let assert Error(form) = result 30 + form 31 + } 32 + 33 + pub fn empty_form_test() { 34 + formz.new() 35 + |> formz.items 36 + |> list.length 37 + |> should.equal(0) 38 + } 39 + 40 + pub fn parse_empty_form_test() { 41 + formz.new() 42 + |> formz.data([]) 43 + |> formz.decodes(1) 44 + |> formz.parse 45 + |> should.equal(Ok(1)) 46 + 47 + formz.new() 48 + |> formz.data([]) 49 + |> formz.decodes("Hello") 50 + |> formz.parse 51 + |> should.equal(Ok("Hello")) 52 + } 53 + 54 + pub fn parse_single_field_form_test() { 55 + formz.new() 56 + |> formz.add(field("first"), text_field()) 57 + |> formz.data([#("first", "world")]) 58 + |> formz.decodes(fn(str) { "hello " <> str }) 59 + |> formz.parse 60 + |> should.equal(Ok("hello world")) 61 + } 62 + 63 + pub fn parse_double_field_form_test() { 64 + formz.new() 65 + |> formz.add(field("first"), text_field()) 66 + |> formz.add(field("second"), text_field()) 67 + |> formz.data([#("first", "hello"), #("second", "world")]) 68 + |> formz.decodes(fn(a) { fn(b) { a <> " " <> b } }) 69 + |> formz.parse 70 + |> should.equal(Ok("hello world")) 71 + } 72 + 73 + pub fn parse_double_field_form_extra_data_test() { 74 + formz.new() 75 + |> formz.add(field("first"), text_field()) 76 + |> formz.add(field("second"), text_field()) 77 + |> formz.data([#("first", "1"), #("second", "2")]) 78 + |> formz.decodes(fn(a) { fn(b) { a <> " " <> b } }) 79 + |> formz.parse 80 + |> should.equal(Ok("1 2")) 81 + 82 + formz.new() 83 + |> formz.add(field("first"), text_field()) 84 + |> formz.add(field("second"), text_field()) 85 + |> formz.data([#("first", "1"), #("second", "2"), #("second", "3")]) 86 + |> formz.decodes(fn(a) { fn(b) { a <> " " <> b } }) 87 + |> formz.parse 88 + |> should.equal(Ok("1 3")) 89 + } 90 + 91 + pub fn integer_field_test() { 92 + formz.new() 93 + |> formz.add(field("first"), integer_field()) 94 + |> formz.data([#("first", " 1 ")]) 95 + |> formz.decodes(fn(i) { i }) 96 + |> formz.parse 97 + |> should.equal(Ok(1)) 98 + } 99 + 100 + pub fn can_decodes_in_any_order_test() { 101 + formz.new() 102 + |> formz.decodes(fn(str) { "hello " <> str }) 103 + |> formz.add(field("first"), text_field()) 104 + |> formz.data([#("first", "world")]) 105 + |> formz.parse 106 + |> should.equal(Ok("hello world")) 107 + 108 + formz.new() 109 + |> formz.add(field("first"), text_field()) 110 + |> formz.data([#("first", "world")]) 111 + |> formz.decodes(fn(str) { "hello " <> str }) 112 + |> formz.parse 113 + |> should.equal(Ok("hello world")) 114 + } 115 + 116 + pub fn parse_single_field_form_with_error_test() { 117 + let assert Error(f) = 118 + formz.new() 119 + |> formz.add(field("first"), integer_field()) 120 + |> formz.data([#("first", "world")]) 121 + |> formz.decodes(fn(_) { 1 }) 122 + |> formz.parse 123 + 124 + let assert [Element(field, _)] = formz.items(f) 125 + field |> should_be_field_with_error("Must be a whole number") 126 + } 127 + 128 + pub fn parse_double_field_form_with_error_test() { 129 + let form = 130 + formz.new() 131 + |> formz.add(field("a"), integer_field()) 132 + |> formz.add(field("b"), integer_field()) 133 + |> formz.decodes(fn(_) { fn(_) { 1 } }) 134 + 135 + let assert Error(f) = 136 + form 137 + |> formz.data([#("a", "not a number"), #("b", "2")]) 138 + |> formz.parse 139 + 140 + let assert [Element(fielda, _), Element(fieldb, _)] = formz.items(f) 141 + fielda |> should_be_field_with_error("Must be a whole number") 142 + fieldb |> should_be_field_no_error 143 + 144 + let assert Error(f) = 145 + form 146 + |> formz.data([#("a", "1"), #("b", "string")]) 147 + |> formz.parse 148 + 149 + let assert [Element(fielda, _), Element(fieldb, _)] = formz.items(f) 150 + fielda |> should_be_field_no_error 151 + fieldb |> should_be_field_with_error("Must be a whole number") 152 + 153 + let assert Error(f) = 154 + form 155 + |> formz.data([#("a", "string"), #("b", "string")]) 156 + |> formz.parse 157 + 158 + let assert [Element(fielda, _), Element(fieldb, _)] = formz.items(f) 159 + fielda |> should_be_field_with_error("Must be a whole number") 160 + fieldb |> should_be_field_with_error("Must be a whole number") 161 + } 162 + 163 + pub fn parse_triple_field_form_with_error_test() { 164 + let form = 165 + formz.new() 166 + |> formz.add(field("a"), integer_field()) 167 + |> formz.add(field("b"), integer_field()) 168 + |> formz.add(field("c"), integer_field()) 169 + |> formz.decodes(fn(_) { fn(_) { fn(_) { 1 } } }) 170 + 171 + let assert Error(f) = 172 + form 173 + |> formz.data([#("a", "1"), #("b", "2"), #("c", "string")]) 174 + |> formz.parse 175 + let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 176 + formz.items(f) 177 + fielda |> should_be_field_no_error 178 + fieldb |> should_be_field_no_error 179 + fieldc |> should_be_field_with_error("Must be a whole number") 180 + 181 + let assert Error(f) = 182 + form 183 + |> formz.data([#("a", "1"), #("b", "string"), #("c", "string")]) 184 + |> formz.parse 185 + let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 186 + formz.items(f) 187 + fielda |> should_be_field_no_error 188 + fieldb |> should_be_field_with_error("Must be a whole number") 189 + fieldc |> should_be_field_with_error("Must be a whole number") 190 + 191 + let assert Error(f) = 192 + form 193 + |> formz.data([#("a", "1"), #("b", "string"), #("c", "3")]) 194 + |> formz.parse 195 + let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 196 + formz.items(f) 197 + fielda |> should_be_field_no_error 198 + fieldb |> should_be_field_with_error("Must be a whole number") 199 + fieldc |> should_be_field_no_error 200 + 201 + let assert Error(f) = 202 + form 203 + |> formz.data([#("a", "string"), #("b", "string"), #("c", "3")]) 204 + |> formz.parse 205 + let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 206 + formz.items(f) 207 + fielda |> should_be_field_with_error("Must be a whole number") 208 + fieldb |> should_be_field_with_error("Must be a whole number") 209 + fieldc |> should_be_field_no_error 210 + 211 + let assert Error(f) = 212 + form 213 + |> formz.data([#("a", "string"), #("b", "2"), #("c", "3")]) 214 + |> formz.parse 215 + let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 216 + formz.items(f) 217 + fielda |> should_be_field_with_error("Must be a whole number") 218 + fieldb |> should_be_field_no_error 219 + fieldc |> should_be_field_no_error 220 + } 221 + 222 + fn should_be_field_no_error(field: field.Field) { 223 + should.equal( 224 + field, 225 + field.Valid( 226 + name: field.name, 227 + label: field.label, 228 + help_text: field.help_text, 229 + value: field.value, 230 + hidden: field.hidden, 231 + disabled: field.disabled, 232 + required: field.required, 233 + ), 234 + ) 235 + } 236 + 237 + fn should_be_field_with_error(field: field.Field, str: String) { 238 + should.equal( 239 + field, 240 + field.Invalid( 241 + name: field.name, 242 + label: field.label, 243 + help_text: field.help_text, 244 + value: field.value, 245 + hidden: field.hidden, 246 + error: str, 247 + disabled: field.disabled, 248 + required: field.required, 249 + ), 250 + ) 251 + } 252 + 253 + pub fn try_test() { 254 + let f = 255 + formz.new() 256 + |> formz.add(field("a"), integer_field()) 257 + |> formz.add(field("b"), integer_field()) 258 + |> formz.add(field("c"), integer_field()) 259 + |> formz.decodes(fn(a) { fn(b) { fn(c) { [a, b, c] } } }) 260 + |> formz.data([#("a", "1"), #("b", "2"), #("c", "3")]) 261 + 262 + // can succeed 263 + formz.parse_try(f, fn(_, _) { Ok(3) }) 264 + |> should.equal(Ok(3)) 265 + 266 + // can change type 267 + formz.parse_try(f, fn(_, _) { Ok("it worked") }) 268 + |> should.equal(Ok("it worked")) 269 + 270 + // can error 271 + formz.parse_try(f, fn(_, form) { Error(form) }) 272 + |> should.equal(Error(f)) 273 + 274 + // can change field 275 + let assert Error(form) = 276 + formz.parse_try(f, fn(_, form) { 277 + Error(formz.update_field(form, "a", field.set_error(_, "woops"))) 278 + }) 279 + let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 280 + formz.items(form) 281 + fielda |> should_be_field_with_error("woops") 282 + fieldb |> should_be_field_no_error 283 + fieldc |> should_be_field_no_error 284 + } 285 + 286 + pub fn sub_form_test() { 287 + let f1 = 288 + formz.new() 289 + |> formz.add(field("a"), integer_field()) 290 + |> formz.add(field("b"), integer_field()) 291 + |> formz.add(field("c"), integer_field()) 292 + |> formz.decodes(fn(a) { fn(b) { fn(c) { #(a, b, c) } } }) 293 + 294 + let f2 = 295 + formz.new() 296 + |> formz.add_form(subform("name"), f1) 297 + |> formz.add(field("d"), integer_field()) 298 + |> formz.decodes(fn(a) { fn(b) { #(a, b) } }) 299 + 300 + f2 301 + |> formz.data([ 302 + #("name.a", "1"), 303 + #("name.b", "2"), 304 + #("name.c", "3"), 305 + #("d", "4"), 306 + ]) 307 + |> formz.parse 308 + |> should.equal(Ok(#(#(1, 2, 3), 4))) 309 + } 310 + 311 + pub fn sub_form_error_tst() { 312 + let f1 = 313 + formz.new() 314 + |> formz.add(field("a"), integer_field()) 315 + |> formz.add(field("b"), integer_field()) 316 + |> formz.add(field("c"), integer_field()) 317 + |> formz.decodes(fn(a) { fn(b) { fn(c) { #(a, b, c) } } }) 318 + 319 + let f2 = 320 + formz.new() 321 + |> formz.add_form(subform("name"), f1) 322 + |> formz.add(field("d"), integer_field()) 323 + |> formz.decodes(fn(a) { fn(b) { #(a, b) } }) 324 + 325 + let assert [ 326 + Set(_, [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)]), 327 + Element(fieldd, _), 328 + ] = 329 + f2 330 + |> formz.data([ 331 + #("name.a", "a"), 332 + #("name.b", "2"), 333 + #("name.c", "3"), 334 + #("d", "4"), 335 + ]) 336 + |> formz.parse 337 + |> get_form_from_error_result 338 + |> formz.items 339 + 340 + fielda |> should_be_field_with_error("Must be a whole number") 341 + fieldb |> should_be_field_no_error 342 + fieldc |> should_be_field_no_error 343 + fieldd |> should_be_field_no_error 344 + }
-260
formz/test/formz/formz_pipes_test.gleam
··· 1 - // import formz/field.{field} 2 - // import formz/formz_pipes as formz 3 - // import formz/input 4 - // import formz/string_generator/fields 5 - // import gleam/list 6 - // import gleeunit 7 - // import gleeunit/should 8 - 9 - // pub fn main() { 10 - // gleeunit.main() 11 - // } 12 - 13 - // pub fn empty_form_test() { 14 - // formz.new() 15 - // |> formz.get_inputs 16 - // |> list.length 17 - // |> should.equal(0) 18 - // } 19 - 20 - // pub fn parse_empty_form_test() { 21 - // formz.new() 22 - // |> formz.data([]) 23 - // |> formz.decodes(1) 24 - // |> formz.parse 25 - // |> should.equal(Ok(1)) 26 - 27 - // formz.new() 28 - // |> formz.data([]) 29 - // |> formz.decodes("Hello") 30 - // |> formz.parse 31 - // |> should.equal(Ok("Hello")) 32 - // } 33 - 34 - // pub fn parse_single_field_form_test() { 35 - // formz.new() 36 - // |> formz.add(field("first", fields.text_field())) 37 - // |> formz.data([#("first", "world")]) 38 - // |> formz.decodes(fn(str) { "hello " <> str }) 39 - // |> formz.parse 40 - // |> should.equal(Ok("hello world")) 41 - // } 42 - 43 - // pub fn parse_double_field_form_test() { 44 - // formz.new() 45 - // |> formz.add(field("first", fields.text_field())) 46 - // |> formz.add(field("second", fields.text_field())) 47 - // |> formz.data([#("first", "hello"), #("second", "world")]) 48 - // |> formz.decodes(fn(a) { fn(b) { a <> " " <> b } }) 49 - // |> formz.parse 50 - // |> should.equal(Ok("hello world")) 51 - // } 52 - 53 - // pub fn parse_double_field_form_extra_data_test() { 54 - // formz.new() 55 - // |> formz.add(field("first", fields.text_field())) 56 - // |> formz.add(field("second", fields.text_field())) 57 - // |> formz.data([#("first", "1"), #("second", "2")]) 58 - // |> formz.decodes(fn(a) { fn(b) { a <> " " <> b } }) 59 - // |> formz.parse 60 - // |> should.equal(Ok("1 2")) 61 - 62 - // formz.new() 63 - // |> formz.add(field("first", fields.text_field())) 64 - // |> formz.add(field("second", fields.text_field())) 65 - // |> formz.data([#("first", "1"), #("second", "2"), #("second", "3")]) 66 - // |> formz.decodes(fn(a) { fn(b) { a <> " " <> b } }) 67 - // |> formz.parse 68 - // |> should.equal(Ok("1 2")) 69 - // } 70 - 71 - // pub fn integer_field_test() { 72 - // formz.new() 73 - // |> formz.add(field("first", fields.integer_field())) 74 - // |> formz.data([#("first", " 1 ")]) 75 - // |> formz.decodes(fn(i) { i }) 76 - // |> formz.parse 77 - // |> should.equal(Ok(1)) 78 - // } 79 - 80 - // pub fn can_decodes_in_any_order_test() { 81 - // formz.new() 82 - // |> formz.decodes(fn(str) { "hello " <> str }) 83 - // |> formz.add(field("first", fields.text_field())) 84 - // |> formz.data([#("first", "world")]) 85 - // |> formz.parse 86 - // |> should.equal(Ok("hello world")) 87 - 88 - // formz.new() 89 - // |> formz.add(field("first", fields.text_field())) 90 - // |> formz.data([#("first", "world")]) 91 - // |> formz.decodes(fn(str) { "hello " <> str }) 92 - // |> formz.parse 93 - // |> should.equal(Ok("hello world")) 94 - // } 95 - 96 - // pub fn parse_single_field_form_with_error_test() { 97 - // let assert Error(f) = 98 - // formz.new() 99 - // |> formz.add(field("first", fields.integer_field())) 100 - // |> formz.data([#("first", "world")]) 101 - // |> formz.decodes(fn(_) { 1 }) 102 - // |> formz.parse 103 - 104 - // let assert [field] = formz.get_inputs(f) 105 - // field |> should_be_field_with_error("Must be a whole number") 106 - // } 107 - 108 - // pub fn parse_double_field_form_with_error_test() { 109 - // let form = 110 - // formz.new() 111 - // |> formz.add(field("a", fields.integer_field())) 112 - // |> formz.add(field("b", fields.integer_field())) 113 - // |> formz.decodes(fn(_) { fn(_) { 1 } }) 114 - 115 - // let assert Error(f) = 116 - // form 117 - // |> formz.data([#("a", "not a number"), #("b", "2")]) 118 - // |> formz.parse 119 - 120 - // let assert [fielda, fieldb] = formz.get_inputs(f) 121 - // fielda |> should_be_field_with_error("Must be a whole number") 122 - // fieldb |> should_be_field_no_error 123 - 124 - // let assert Error(f) = 125 - // form 126 - // |> formz.data([#("a", "1"), #("b", "string")]) 127 - // |> formz.parse 128 - 129 - // let assert [fielda, fieldb] = formz.get_inputs(f) 130 - // fielda |> should_be_field_no_error 131 - // fieldb |> should_be_field_with_error("Must be a whole number") 132 - 133 - // let assert Error(f) = 134 - // form 135 - // |> formz.data([#("a", "string"), #("b", "string")]) 136 - // |> formz.parse 137 - 138 - // let assert [fielda, fieldb] = formz.get_inputs(f) 139 - // fielda |> should_be_field_with_error("Must be a whole number") 140 - // fieldb |> should_be_field_with_error("Must be a whole number") 141 - // } 142 - 143 - // pub fn parse_triple_field_form_with_error_test() { 144 - // let form = 145 - // formz.new() 146 - // |> formz.add(field("a", fields.integer_field())) 147 - // |> formz.add(field("b", fields.integer_field())) 148 - // |> formz.add(field("c", fields.integer_field())) 149 - // |> formz.decodes(fn(_) { fn(_) { fn(_) { 1 } } }) 150 - 151 - // let assert Error(f) = 152 - // form 153 - // |> formz.data([#("a", "1"), #("b", "2"), #("c", "string")]) 154 - // |> formz.parse 155 - // let assert [fielda, fieldb, fieldc] = formz.get_inputs(f) 156 - // fielda |> should_be_field_no_error 157 - // fieldb |> should_be_field_no_error 158 - // fieldc |> should_be_field_with_error("Must be a whole number") 159 - 160 - // let assert Error(f) = 161 - // form 162 - // |> formz.data([#("a", "1"), #("b", "string"), #("c", "string")]) 163 - // |> formz.parse 164 - // let assert [fielda, fieldb, fieldc] = formz.get_inputs(f) 165 - // fielda |> should_be_field_no_error 166 - // fieldb |> should_be_field_with_error("Must be a whole number") 167 - // fieldc |> should_be_field_with_error("Must be a whole number") 168 - 169 - // let assert Error(f) = 170 - // form 171 - // |> formz.data([#("a", "1"), #("b", "string"), #("c", "3")]) 172 - // |> formz.parse 173 - // let assert [fielda, fieldb, fieldc] = formz.get_inputs(f) 174 - // fielda |> should_be_field_no_error 175 - // fieldb |> should_be_field_with_error("Must be a whole number") 176 - // fieldc |> should_be_field_no_error 177 - 178 - // let assert Error(f) = 179 - // form 180 - // |> formz.data([#("a", "string"), #("b", "string"), #("c", "3")]) 181 - // |> formz.parse 182 - // let assert [fielda, fieldb, fieldc] = formz.get_inputs(f) 183 - // fielda |> should_be_field_with_error("Must be a whole number") 184 - // fieldb |> should_be_field_with_error("Must be a whole number") 185 - // fieldc |> should_be_field_no_error 186 - 187 - // let assert Error(f) = 188 - // form 189 - // |> formz.data([#("a", "string"), #("b", "2"), #("c", "3")]) 190 - // |> formz.parse 191 - // let assert [fielda, fieldb, fieldc] = formz.get_inputs(f) 192 - // fielda |> should_be_field_with_error("Must be a whole number") 193 - // fieldb |> should_be_field_no_error 194 - // fieldc |> should_be_field_no_error 195 - // } 196 - 197 - // fn should_be_field_no_error(field: input.Input(String)) { 198 - // should.equal( 199 - // field, 200 - // input.Valid( 201 - // name: field.name, 202 - // label: field.label, 203 - // help_text: field.help_text, 204 - // value: field.value, 205 - // widget: field.widget, 206 - // hidden: field.hidden, 207 - // disabled: field.disabled, 208 - // required: field.required, 209 - // ), 210 - // ) 211 - // } 212 - 213 - // fn should_be_field_with_error(field: input.Input(String), str: String) { 214 - // should.equal( 215 - // field, 216 - // input.Invalid( 217 - // name: field.name, 218 - // label: field.label, 219 - // help_text: field.help_text, 220 - // value: field.value, 221 - // widget: field.widget, 222 - // hidden: field.hidden, 223 - // error: str, 224 - // disabled: field.disabled, 225 - // required: field.required, 226 - // ), 227 - // ) 228 - // } 229 - 230 - // pub fn try_test() { 231 - // let f = 232 - // formz.new() 233 - // |> formz.add(field("a", fields.integer_field())) 234 - // |> formz.add(field("b", fields.integer_field())) 235 - // |> formz.add(field("c", fields.integer_field())) 236 - // |> formz.decodes(fn(a) { fn(b) { fn(c) { [a, b, c] } } }) 237 - // |> formz.data([#("a", "1"), #("b", "2"), #("c", "3")]) 238 - 239 - // // can succeed 240 - // formz.try(f, fn(_, _) { Ok(3) }) 241 - // |> should.equal(Ok(3)) 242 - 243 - // // can change type 244 - // formz.try(f, fn(_, _) { Ok("it worked") }) 245 - // |> should.equal(Ok("it worked")) 246 - 247 - // // can error 248 - // formz.try(f, fn(_, form) { Error(form) }) 249 - // |> should.equal(Error(f)) 250 - 251 - // // can change field 252 - // let assert Error(form) = 253 - // formz.try(f, fn(_, form) { 254 - // Error(formz.update_input(form, "a", input.set_error(_, "woops"))) 255 - // }) 256 - // let assert [fielda, fieldb, fieldc] = formz.get_inputs(form) 257 - // fielda |> should_be_field_with_error("woops") 258 - // fieldb |> should_be_field_no_error 259 - // fieldc |> should_be_field_no_error 260 - // }
+51 -50
formz/test/formz/formz_use_test.gleam
··· 1 1 import formz/definition 2 2 import formz/field.{field} 3 - import formz/formz_use.{Item, Set} as formz 3 + import formz/formz_use.{Element, Set} as formz 4 + import formz/subform.{subform} 4 5 import formz/validation 5 - import gleam/io 6 - import gleam/option 7 6 import gleeunit 8 7 import gleeunit/should 9 8 10 9 pub fn text_field() { 11 - field.Definition(field.widget(fn(_, _) { "" }), validation.string, "") 10 + definition.Definition(fn(_, _) { "" }, validation.string, "") 12 11 } 13 12 14 - pub fn int_field() { 15 - field.Definition(field.widget(fn(_, _) { "" }), validation.int, 0) 13 + pub fn integer_field() { 14 + definition.Definition(fn(_, _) { "" }, validation.int, 0) 16 15 } 17 16 18 17 pub fn float_field() { 19 - field.Definition(field.widget(fn(_, _) { "" }), validation.number, 0.0) 18 + definition.Definition(fn(_, _) { "" }, validation.number, 0.0) 20 19 } 21 20 22 - fn should_be_field_no_error(field: field.Field(String)) { 21 + fn should_be_field_no_error(field: field.Field) { 23 22 should.equal( 24 23 field, 25 24 field.Valid( ··· 27 26 label: field.label, 28 27 help_text: field.help_text, 29 28 value: field.value, 30 - widget: field.widget, 31 29 hidden: field.hidden, 32 30 disabled: field.disabled, 33 31 required: field.required, ··· 35 33 ) 36 34 } 37 35 38 - fn should_be_field_with_error(field: field.Field(String), str: String) { 36 + fn should_be_field_with_error(field: field.Field, str: String) { 39 37 should.equal( 40 38 field, 41 39 field.Invalid( ··· 43 41 label: field.label, 44 42 help_text: field.help_text, 45 43 value: field.value, 46 - widget: field.widget, 47 44 hidden: field.hidden, 48 45 error: str, 49 46 disabled: field.disabled, ··· 90 87 |> definition.validates(validation.must_be_longer_than(3)), 91 88 ) 92 89 93 - use b <- formz.with(field(named: "b"), int_field()) 90 + use b <- formz.with(field(named: "b"), integer_field()) 94 91 use c <- formz.with( 95 92 field(named: "c") 96 93 |> field.set_name("c") ··· 103 100 104 101 pub fn empty_form_test() { 105 102 empty_form(1) 106 - |> formz.get_items 103 + |> formz.items 107 104 |> should.equal([]) 108 105 } 109 106 ··· 190 187 pub fn parse_single_field_form_with_error_test() { 191 188 let assert Error(f) = 192 189 { 193 - use a <- formz.with(field("a"), int_field()) 190 + use a <- formz.with(field("a"), integer_field()) 194 191 formz.create_form(a) 195 192 } 196 193 |> formz.data([#("first", "world")]) 197 194 |> formz.parse 198 195 199 - let assert [formz.Item(field)] = formz.get_items(f) 196 + let assert [Element(field, _)] = formz.items(f) 200 197 field |> should_be_field_with_error("Must be a whole number") 201 198 } 202 199 203 200 pub fn parse_triple_field_form_with_error_test() { 204 - let assert [Item(fielda), Item(fieldb), Item(fieldc)] = 201 + let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 205 202 three_field_form() 206 203 |> formz.data([#("a", "xxxx"), #("b", "1"), #("c", "x")]) 207 204 |> formz.parse 208 205 |> get_form_from_error_result 209 - |> formz.get_items 206 + |> formz.items 210 207 211 - fielda |> io.debug |> should_be_field_no_error 212 - fieldb |> io.debug |> should_be_field_no_error 213 - fieldc |> io.debug |> should_be_field_with_error("Must be a number") 208 + fielda |> should_be_field_no_error 209 + fieldb |> should_be_field_no_error 210 + fieldc |> should_be_field_with_error("Must be a number") 214 211 215 - let assert [Item(fielda), Item(fieldb), Item(fieldc)] = 212 + let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 216 213 three_field_form() 217 214 |> formz.data([#("a", "string"), #("b", "string"), #("c", "string")]) 218 215 |> formz.parse 219 216 |> get_form_from_error_result 220 - |> formz.get_items 217 + |> formz.items 221 218 fielda |> should_be_field_no_error 222 219 fieldb |> should_be_field_with_error("Must be a whole number") 223 220 fieldc |> should_be_field_with_error("Must be a number") 224 221 225 - let assert [Item(fielda), Item(fieldb), Item(fieldc)] = 222 + let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 226 223 three_field_form() 227 224 |> formz.data([#("a", "string"), #("b", "string"), #("c", "3.4")]) 228 225 |> formz.parse 229 226 |> get_form_from_error_result 230 - |> formz.get_items 227 + |> formz.items 231 228 fielda |> should_be_field_no_error 232 229 fieldb |> should_be_field_with_error("Must be a whole number") 233 230 fieldc |> should_be_field_no_error 234 231 235 - let assert [Item(fielda), Item(fieldb), Item(fieldc)] = 232 + let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 236 233 three_field_form() 237 234 |> formz.data([#("a", "."), #("b", "string"), #("c", "3.4")]) 238 235 |> formz.parse 239 236 |> get_form_from_error_result 240 - |> formz.get_items 237 + |> formz.items 241 238 fielda |> should_be_field_with_error("Must be longer than 3") 242 239 fieldb |> should_be_field_with_error("Must be a whole number") 243 240 fieldc |> should_be_field_no_error 244 241 245 - let assert [Item(fielda), Item(fieldb), Item(fieldc)] = 242 + let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 246 243 three_field_form() 247 244 |> formz.data([#("a", "."), #("b", "1"), #("c", "3.4")]) 248 245 |> formz.parse 249 246 |> get_form_from_error_result 250 - |> formz.get_items 247 + |> formz.items 251 248 fielda |> should_be_field_with_error("Must be longer than 3") 252 249 fieldb |> should_be_field_no_error 253 250 fieldc |> should_be_field_no_error ··· 255 252 256 253 pub fn sub_form_test() { 257 254 let f1 = { 258 - use a <- formz.with(field("a"), int_field()) 259 - use b <- formz.with(field("b"), int_field()) 260 - use c <- formz.with(field("c"), int_field()) 255 + use a <- formz.with(field("a"), integer_field()) 256 + use b <- formz.with(field("b"), integer_field()) 257 + use c <- formz.with(field("c"), integer_field()) 261 258 262 259 formz.create_form(#(a, b, c)) 263 260 } 264 261 265 262 let f2 = { 266 - use a <- formz.sub_form("name", "Name", f1) 267 - use b <- formz.with(field("d"), int_field()) 263 + use a <- formz.with_form(subform("name"), f1) 264 + use b <- formz.with(field("d"), integer_field()) 268 265 269 266 formz.create_form(#(a, b)) 270 267 } ··· 282 279 283 280 pub fn sub_form_error_test() { 284 281 let f1 = { 285 - use a <- formz.with(field("a"), int_field()) 286 - use b <- formz.with(field("b"), int_field()) 287 - use c <- formz.with(field("c"), int_field()) 282 + use a <- formz.with(field("a"), integer_field()) 283 + use b <- formz.with(field("b"), integer_field()) 284 + use c <- formz.with(field("c"), integer_field()) 288 285 289 286 formz.create_form(#(a, b, c)) 290 287 } 291 288 292 289 let f2 = { 293 - use a <- formz.sub_form("name", "Name", f1) 294 - use b <- formz.with(field("d"), int_field()) 290 + use a <- formz.with_form(subform("name"), f1) 291 + use b <- formz.with(field("d"), integer_field()) 295 292 296 293 formz.create_form(#(a, b)) 297 294 } 298 295 299 - let assert [Set(_, [Item(inputa), Item(inputb), Item(inputc)]), Item(inputd)] = 296 + let assert [ 297 + Set(_, [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)]), 298 + Element(fieldd, _), 299 + ] = 300 300 f2 301 301 |> formz.data([ 302 302 #("name.a", "a"), ··· 306 306 ]) 307 307 |> formz.parse 308 308 |> get_form_from_error_result 309 - |> formz.get_items 309 + |> formz.items 310 310 311 - inputa |> should_be_field_with_error("Must be a whole number") 312 - inputb |> should_be_field_no_error 313 - inputc |> should_be_field_no_error 314 - inputd |> should_be_field_no_error 311 + fielda |> should_be_field_with_error("Must be a whole number") 312 + fieldb |> should_be_field_no_error 313 + fieldc |> should_be_field_no_error 314 + fieldd |> should_be_field_no_error 315 315 } 316 316 317 317 pub fn decoded_and_try_test() { ··· 320 320 |> formz.data([#("a", "string"), #("b", "2"), #("c", "3.0")]) 321 321 322 322 // can succeed 323 - formz.try(f, fn(_, _) { Ok(3) }) 323 + formz.parse_try(f, fn(_, _) { Ok(3) }) 324 324 |> should.equal(Ok(3)) 325 325 326 326 // can change type 327 - formz.try(f, fn(_, _) { Ok("it worked") }) 327 + formz.parse_try(f, fn(_, _) { Ok("it worked") }) 328 328 |> should.equal(Ok("it worked")) 329 329 330 330 // can error 331 - formz.try(f, fn(_, form) { Error(form) }) 331 + formz.parse_try(f, fn(_, form) { Error(form) }) 332 332 |> should.equal(Error(f)) 333 333 334 334 // can change field 335 335 let assert Error(form) = 336 - formz.try(f, fn(_, form) { 336 + formz.parse_try(f, fn(_, form) { 337 337 form 338 - |> formz.update_input("a", field.set_error(_, "woops")) 338 + |> formz.update_field("a", field.set_error(_, "woops")) 339 339 |> Error 340 340 }) 341 - let assert [Item(fielda), Item(fieldb), Item(fieldc)] = formz.get_items(form) 341 + let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 342 + formz.items(form) 342 343 fielda |> should_be_field_with_error("woops") 343 344 fieldb |> should_be_field_no_error 344 345 fieldc |> should_be_field_no_error
+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.get_items |> do_get_inputs([]) |> list.reverse 50 + form |> formz.formitems |> 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.Item(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 }
+12 -9
formz_demo/src/formz_demo/examples/all_the_inputs.gleam
··· 1 1 import formz/field.{field} 2 2 import formz/formz_use as formz 3 - import formz_string/fields 3 + import formz_string/definitions 4 4 5 5 pub fn make_form() { 6 6 let choices = [#("Yes", True), #("Maybe", True), #("No", False)] 7 7 8 - use a <- formz.with(field("a"), fields.text_field()) 9 - use b <- formz.with(field("b"), fields.integer_field()) 10 - use c <- formz.with(field("c"), fields.number_field()) 11 - use d <- formz.with(field("d"), fields.boolean_field()) 12 - use e <- formz.with(field("e"), fields.email_field()) 13 - use f <- formz.with(field("g"), fields.enum_field(letters())) 14 - use g <- formz.with(field("h"), fields.indexed_enum_field(choices)) 15 - use h <- formz.with(field("i"), fields.list_field(["Dog", "Cat", "Bird"])) 8 + use a <- formz.with(field("a"), definitions.text_field()) 9 + use b <- formz.with(field("b"), definitions.integer_field()) 10 + use c <- formz.with(field("c"), definitions.number_field()) 11 + use d <- formz.with(field("d"), definitions.boolean_field()) 12 + use e <- formz.with(field("e"), definitions.email_field()) 13 + use f <- formz.with(field("g"), definitions.enum_field(letters())) 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 + ) 16 19 17 20 formz.create_form(#(a, b, c, d, e, f, g, h)) 18 21 }
+9 -22
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_lustre/fields 3 + import formz/subform.{subform} 4 + import formz_lustre/definitions 4 5 import formz_lustre/simple 5 6 import gleam/list 6 7 import lustre/attribute 7 8 import lustre/element/html 8 9 9 10 pub fn make_form() { 10 - use billing_address <- formz.sub_form( 11 - "billing", 12 - "Billing Address", 13 - address_form(), 14 - ) 15 - use shipping_address <- formz.sub_form( 16 - "shipping", 17 - "Shipping Address", 18 - address_form(), 19 - ) 11 + use billing_address <- formz.with_form(subform("billing"), address_form()) 12 + use shipping_address <- formz.with_form(subform("shipping"), address_form()) 20 13 21 14 formz.create_form(#(billing_address, shipping_address)) 22 15 } 23 16 24 17 fn address_form() { 25 - use street <- formz.with(field(named: "street"), fields.text_field()) 26 - use city <- formz.with(field(named: "city"), fields.text_field()) 27 - use state <- formz.with( 28 - field(named: "state"), 29 - fields.list_field(states_list()), 30 - ) 31 - use postal_code <- formz.with( 32 - field(named: "postal_code"), 33 - fields.text_field(), 34 - ) 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()) 35 22 36 23 formz.create_form(Address(street:, city:, state:, postal_code:)) 37 24 } 38 25 39 26 pub fn format_form(form) { 40 - let assert Ok(formz.Set(_, billing_inputs)) = formz.get_item(form, "billing") 27 + let assert Ok(formz.Set(_, billing_inputs)) = formz.get(form, "billing") 41 28 html.div( 42 29 [ 43 30 attribute.role("group"),
+2 -2
formz_demo/src/formz_demo/examples/hello_world.gleam
··· 1 1 import formz/field.{field} 2 2 import formz/formz_use as formz 3 - import formz_string/fields 3 + import formz_string/definitions 4 4 5 5 pub fn make_form() { 6 - use name <- formz.with(field("name"), fields.text_field()) 6 + use name <- formz.with(field("name"), definitions.text_field()) 7 7 formz.create_form("Hello " <> name) 8 8 }
+13 -13
formz_demo/src/formz_demo/examples/labels.gleam
··· 1 - import formz/definition as d 1 + import formz/definition 2 2 import formz/field.{field} 3 3 import formz/formz_use as formz 4 4 import formz/validation 5 - import formz_string/fields 5 + import formz_string/definitions as defs 6 6 7 7 pub fn make_form() { 8 - use name <- formz.with(field(named: "name"), fields.text_field()) 8 + use name <- formz.with(field(named: "name"), is: defs.text_field()) 9 9 use age <- formz.with( 10 10 field("age") |> field.set_label("Age"), 11 - fields.integer_field(), 11 + is: defs.integer_field(), 12 12 ) 13 13 use height <- formz.with( 14 14 field("height") 15 15 |> field.set_label("Height (cm)") 16 16 |> field.set_help_text("Please enter your height in centimeters"), 17 - fields.integer_field(), 17 + is: defs.integer_field(), 18 18 ) 19 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 - // fields.text_field() 26 - // |> d.validates(validation.must_be_longer_than(3)), 27 - // ) 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)), 27 + ) 28 28 29 29 formz.create_form(#(name, age, height)) 30 30 }
+6 -6
formz_demo/src/formz_demo/examples/login.gleam
··· 1 1 import formz/definition 2 2 import formz/field.{field} 3 3 import formz/formz_use as formz 4 - import formz_string/fields 4 + import formz_string/definitions 5 5 import formz_string/widgets 6 6 import wisp 7 7 ··· 14 14 } 15 15 16 16 pub fn make_form() { 17 - use username <- formz.with(field("username"), fields.text_field()) 17 + use username <- formz.with(field("username"), definitions.text_field()) 18 18 use password <- formz.with( 19 19 field("password"), 20 - fields.text_field() |> definition.set_widget(widgets.password_widget()), 20 + definitions.text_field() |> definition.set_widget(widgets.password_widget()), 21 21 ) 22 22 23 23 formz.create_form(Credentials(username, password)) 24 24 } 25 25 26 26 pub fn handle_post(formdata: wisp.FormData, form) { 27 - use cred, form <- formz.try(form |> formz.data(formdata.values)) 27 + use cred, form <- formz.parse_try(form |> formz.data(formdata.values)) 28 28 29 29 case cred { 30 30 Credentials("admin", "l33t") -> Ok(User(cred.username)) 31 31 Credentials("admin", _) -> 32 32 form 33 - |> formz.update_input("password", field.set_error(_, "wrong password")) 33 + |> formz.update_field("password", field.set_error(_, "wrong password")) 34 34 |> Error 35 35 Credentials(_, _) -> 36 36 form 37 - |> formz.update_input("username", field.set_error(_, "wrong username")) 37 + |> formz.update_field("username", field.set_error(_, "wrong username")) 38 38 |> Error 39 39 } 40 40 }
+11 -15
formz_demo/src/formz_demo/examples/sub_form.gleam
··· 1 1 import formz/field.{field} 2 + import formz/fieldset.{fieldset} 2 3 import formz/formz_use as formz 3 - import formz_string/fields 4 + import formz_string/definitions 4 5 5 6 pub fn make_form() { 6 - use billing_address <- formz.sub_form( 7 - "billing", 8 - "Billing Address", 9 - address_form(), 10 - ) 11 - use shipping_address <- formz.sub_form( 12 - "shipping", 13 - "Shipping Address", 14 - address_form(), 15 - ) 7 + use billing_address <- formz.with_form(fieldset("billing"), address_form()) 8 + use shipping_address <- formz.with_form(fieldset("shipping"), address_form()) 16 9 17 10 formz.create_form(#(billing_address, shipping_address)) 18 11 } 19 12 20 13 fn address_form() { 21 - use street <- formz.with(field("street"), fields.text_field()) 22 - use city <- formz.with(field("city"), fields.text_field()) 23 - use state <- formz.with(field("state"), fields.list_field(states_list())) 24 - use postal_code <- formz.with(field.field("postal_code"), fields.text_field()) 14 + use street <- formz.with(field("street"), definitions.text_field()) 15 + use city <- formz.with(field("city"), definitions.text_field()) 16 + use state <- formz.with(field("state"), definitions.list_field(states_list())) 17 + use postal_code <- formz.with( 18 + field.field("postal_code"), 19 + definitions.text_field(), 20 + ) 25 21 26 22 formz.create_form(Address(street:, city:, state:, postal_code:)) 27 23 }
+1 -1
formz_lustre/src/formz_lustre/fields.gleam formz_lustre/src/formz_lustre/definitions.gleam
··· 1 - import formz/field.{Definition} 1 + import formz/definition.{Definition} 2 2 import formz/validation 3 3 import formz_lustre/widgets 4 4 import gleam/list
+7 -6
formz_lustre/src/formz_lustre/simple.gleam
··· 1 - import formz/field.{WidgetArgs} 1 + import formz/field 2 2 import formz/formz_use as formz 3 + import formz/widget 3 4 import gleam/list 4 5 import gleam/string 5 6 import lustre/attribute ··· 8 9 9 10 pub fn generate_form(form) -> element.Element(msg) { 10 11 form 11 - |> formz.get_items 12 + |> formz.items 12 13 |> list.map(generate_item) 13 14 |> element.fragment 14 15 } ··· 17 18 item: formz.FormItem(element.Element(msg)), 18 19 ) -> element.Element(msg) { 19 20 case item { 20 - formz.Item(f) if f.hidden == True -> 21 + formz.Element(f, _) if f.hidden == True -> 21 22 html.input([ 22 23 attribute.type_("hidden"), 23 24 attribute.name(f.name), 24 25 attribute.value(f.value), 25 26 ]) 26 - formz.Item(f) -> { 27 + formz.Element(f, make_widget) -> { 27 28 let label_el = html.label([], [html.text(f.label), html.text(": ")]) 28 29 29 30 let description_el = case string.is_empty(f.help_text) { ··· 33 34 } 34 35 let widget_el = 35 36 html.span([attribute.class("widget")], [ 36 - field.run_widget( 37 + make_widget( 37 38 f, 38 - WidgetArgs(id: f.name, labelled_by: field.Element), 39 + widget.Args(id: f.name, labelled_by: widget.LabelledByLabelFor), 39 40 ), 40 41 ]) 41 42
+75 -97
formz_lustre/src/formz_lustre/widgets.gleam
··· 1 - import formz/field.{type Field, type WidgetArgs, WidgetArgs} 1 + import formz/field.{type Field} 2 + import formz/widget 2 3 import gleam/list 3 4 import gleam/string 4 5 import lustre/attribute ··· 20 21 } 21 22 22 23 fn aria_label_attr( 23 - labelled_by: field.InputLabelled, 24 + labelled_by: widget.LabelledBy, 24 25 label: String, 25 26 ) -> attribute.Attribute(msg) { 26 27 case labelled_by { 27 - field.Element -> attribute.none() 28 - field.Id(id) -> attribute.attribute("aria-labelledby", id) 29 - field.Value -> 28 + widget.LabelledByLabelFor -> attribute.none() 29 + widget.LabelledByElementWithId(id) -> 30 + attribute.attribute("aria-labelledby", id) 31 + widget.LabelledByFieldValue -> 30 32 case label { 31 33 "" -> attribute.none() 32 34 _ -> attribute.attribute("aria-label", label) ··· 42 44 } 43 45 44 46 pub fn checkbox_widget() { 45 - field.widget( 46 - fn(input: Field(element.Element(msg)), args: WidgetArgs) -> element.Element( 47 - msg, 48 - ) { 49 - html.input([ 50 - attribute.type_("checkbox"), 51 - name_attr(input.name), 52 - id_attr(args.id), 53 - attribute.checked(input.value == "on"), 54 - aria_label_attr(args.labelled_by, input.label), 55 - ]) 56 - }, 57 - ) 47 + fn(input: Field, args: widget.Args) -> element.Element(msg) { 48 + html.input([ 49 + attribute.type_("checkbox"), 50 + name_attr(input.name), 51 + id_attr(args.id), 52 + attribute.checked(input.value == "on"), 53 + aria_label_attr(args.labelled_by, input.label), 54 + ]) 55 + } 58 56 } 59 57 60 58 pub fn password_widget() { 61 - field.widget( 62 - fn(input: Field(element.Element(msg)), args: WidgetArgs) -> element.Element( 63 - msg, 64 - ) { 65 - html.input([ 66 - attribute.type_("password"), 67 - name_attr(input.name), 68 - id_attr(args.id), 69 - // value_attr(input.value), 70 - aria_label_attr(args.labelled_by, input.label), 71 - ]) 72 - }, 73 - ) 59 + fn(input: Field, args: widget.Args) -> element.Element(msg) { 60 + html.input([ 61 + attribute.type_("password"), 62 + name_attr(input.name), 63 + id_attr(args.id), 64 + // value_attr(input.value), 65 + aria_label_attr(args.labelled_by, input.label), 66 + ]) 67 + } 74 68 } 75 69 76 70 pub fn text_widget() { ··· 78 72 } 79 73 80 74 pub fn text_like_widget(type_: String) { 81 - field.widget( 82 - fn(input: Field(element.Element(msg)), args: WidgetArgs) -> element.Element( 83 - msg, 84 - ) { 85 - html.input([ 86 - attribute.type_(type_), 87 - name_attr(input.name), 88 - id_attr(args.id), 89 - value_attr(input.value), 90 - aria_label_attr(args.labelled_by, input.label), 91 - ]) 92 - }, 93 - ) 75 + fn(input: Field, args: widget.Args) -> element.Element(msg) { 76 + html.input([ 77 + attribute.type_(type_), 78 + name_attr(input.name), 79 + id_attr(args.id), 80 + value_attr(input.value), 81 + aria_label_attr(args.labelled_by, input.label), 82 + ]) 83 + } 94 84 } 95 85 96 86 pub fn textarea_widget() { 97 - field.widget( 98 - fn(input: Field(element.Element(msg)), args: WidgetArgs) -> element.Element( 99 - msg, 100 - ) { 101 - html.textarea( 102 - [ 103 - name_attr(input.name), 104 - id_attr(args.id), 105 - aria_label_attr(args.labelled_by, input.label), 106 - ], 107 - input.value, 108 - ) 109 - }, 110 - ) 87 + fn(input: Field, args: widget.Args) -> element.Element(msg) { 88 + html.textarea( 89 + [ 90 + name_attr(input.name), 91 + id_attr(args.id), 92 + aria_label_attr(args.labelled_by, input.label), 93 + ], 94 + input.value, 95 + ) 96 + } 111 97 } 112 98 113 99 pub fn hidden_widget() { 114 - field.widget( 115 - fn(input: Field(element.Element(msg)), _args: WidgetArgs) -> element.Element( 116 - msg, 117 - ) { 118 - html.input([ 119 - attribute.type_("hidden"), 120 - name_attr(input.name), 121 - value_attr(input.value), 122 - ]) 123 - }, 124 - ) 100 + fn(input: Field, _args: widget.Args) -> element.Element(msg) { 101 + html.input([ 102 + attribute.type_("hidden"), 103 + name_attr(input.name), 104 + value_attr(input.value), 105 + ]) 106 + } 125 107 } 126 108 127 109 pub fn select_widget(variants: List(#(String, value))) { 128 - field.widget( 129 - fn(input: Field(element.Element(msg)), args: WidgetArgs) -> element.Element( 130 - msg, 131 - ) { 132 - html.select( 133 - [attribute.name(input.name)], 110 + fn(input: Field, args: widget.Args) -> element.Element(msg) { 111 + html.select( 112 + [attribute.name(input.name)], 113 + list.map(variants, fn(variant) { 114 + let val = string.inspect(variant.1) 115 + html.option( 116 + [attribute.value(val), attribute.selected(input.value == val)], 117 + variant.0, 118 + ) 119 + }), 120 + ) 121 + 122 + html.select( 123 + [ 124 + name_attr(input.name), 125 + id_attr(args.id), 126 + aria_label_attr(args.labelled_by, input.label), 127 + ], 128 + list.flatten([ 129 + [html.option([attribute.value("")], "Select..."), html.hr([])], 134 130 list.map(variants, fn(variant) { 135 131 let val = string.inspect(variant.1) 136 132 html.option( 137 - [attribute.value(val), attribute.selected(input.value == val)], 133 + [value_attr(val), attribute.selected(input.value == val)], 138 134 variant.0, 139 135 ) 140 136 }), 141 - ) 142 - 143 - html.select( 144 - [ 145 - name_attr(input.name), 146 - id_attr(args.id), 147 - aria_label_attr(args.labelled_by, input.label), 148 - ], 149 - list.flatten([ 150 - [html.option([attribute.value("")], "Select..."), html.hr([])], 151 - list.map(variants, fn(variant) { 152 - let val = string.inspect(variant.1) 153 - html.option( 154 - [value_attr(val), attribute.selected(input.value == val)], 155 - variant.0, 156 - ) 157 - }), 158 - ]), 159 - ) 160 - }, 161 - ) 137 + ]), 138 + ) 139 + } 162 140 }
+19 -20
formz_lustre/test/formz_lustre/fields_test.gleam
··· 1 - import formz/field 1 + import formz/definition 2 2 import gleeunit 3 3 import gleeunit/should 4 4 5 - import formz_lustre/fields 6 - import formz_string/fields as string_fields 5 + import formz_lustre/definitions 6 + import formz_string/definitions as string_kinds 7 7 8 8 pub fn main() { 9 9 gleeunit.main() 10 10 } 11 11 12 12 fn fields_should_be_equal_except_widget( 13 - field1: field.Definition(format1, output), 14 - field2: field.Definition(format2, output), 13 + field1: definition.Definition(format1, output), 14 + field2: definition.Definition(format2, output), 15 15 ) { 16 16 field1.placeholder |> should.equal(field2.placeholder) 17 17 field1.transform |> should.equal(field2.transform) 18 18 } 19 19 20 20 pub fn text_field_test() { 21 - let string_field = string_fields.text_field() 22 - let field = fields.text_field() 21 + let string_field = string_kinds.text_field() 22 + let field = definitions.text_field() 23 23 24 24 fields_should_be_equal_except_widget(field, string_field) 25 25 } 26 26 27 27 pub fn email_field_test() { 28 - let string_field = string_fields.email_field() 29 - let field = fields.email_field() 28 + let string_field = string_kinds.email_field() 29 + let field = definitions.email_field() 30 30 31 31 fields_should_be_equal_except_widget(field, string_field) 32 32 } 33 33 34 34 pub fn number_field_test() { 35 - let string_field = string_fields.number_field() 36 - let field = fields.number_field() 35 + let string_field = string_kinds.number_field() 36 + let field = definitions.number_field() 37 37 38 38 fields_should_be_equal_except_widget(field, string_field) 39 39 } 40 40 41 41 pub fn integer_field_test() { 42 - let string_field = string_fields.integer_field() 43 - let field = fields.integer_field() 42 + let string_field = string_kinds.integer_field() 43 + let field = definitions.integer_field() 44 44 45 45 fields_should_be_equal_except_widget(field, string_field) 46 46 } 47 47 48 48 pub fn boolean_field_test() { 49 - let string_field = string_fields.boolean_field() 50 - let field = fields.boolean_field() 49 + let string_field = string_kinds.boolean_field() 50 + let field = definitions.boolean_field() 51 51 52 52 fields_should_be_equal_except_widget(field, string_field) 53 53 } 54 54 55 55 pub fn enum_field_test() { 56 - let string_field = string_fields.enum_field([#("a", "A"), #("b", "B")]) 57 - let field = fields.enum_field([#("a", "A"), #("b", "B")]) 56 + let string_field = string_kinds.enum_field([#("a", "A"), #("b", "B")]) 57 + let field = definitions.enum_field([#("a", "A"), #("b", "B")]) 58 58 59 59 fields_should_be_equal_except_widget(field, string_field) 60 60 } 61 61 62 62 pub fn indexed_enum_field_test() { 63 - let string_field = 64 - string_fields.indexed_enum_field([#("a", "A"), #("b", "B")]) 65 - let field = fields.indexed_enum_field([#("a", "A"), #("b", "B")]) 63 + let string_field = string_kinds.indexed_enum_field([#("a", "A"), #("b", "B")]) 64 + let field = definitions.indexed_enum_field([#("a", "A"), #("b", "B")]) 66 65 67 66 fields_should_be_equal_except_widget(field, string_field) 68 67 }
+27 -28
formz_lustre/test/formz_lustre/widgets_test.gleam
··· 1 - import formz/input.{WidgetArgs} 1 + import formz/field 2 + import formz/widget 2 3 import gleeunit 3 4 import gleeunit/should 4 5 import lustre/element ··· 27 28 string string_widget, 28 29 widget widget, 29 30 ) { 30 - let string_input = 31 - input.Valid( 31 + let string_field = 32 + field.Valid( 32 33 name:, 33 34 label:, 34 35 help_text:, 35 - widget: string_widget, 36 36 hidden:, 37 37 value:, 38 38 disabled:, 39 39 required:, 40 40 ) 41 - let input = 42 - input.Valid( 41 + let field = 42 + field.Valid( 43 43 name:, 44 44 label:, 45 45 help_text:, 46 - widget: widget, 47 46 hidden:, 48 47 value:, 49 48 disabled:, 50 49 required:, 51 50 ) 52 51 53 - input.widget(input, args) 52 + widget(field, args) 54 53 |> convert_to_string 55 - |> should.equal(string_input.widget(string_input, args)) 54 + |> should.equal(string_widget(string_field, args)) 56 55 } 57 56 58 57 pub fn text_widget_test() { ··· 66 65 disabled: False, 67 66 required: True, 68 67 value: "", 69 - args: WidgetArgs("id", labelled_by: input.Element), 68 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 70 69 ) 71 70 72 71 test_inputs( ··· 79 78 disabled: False, 80 79 required: True, 81 80 value: "", 82 - args: WidgetArgs("id", labelled_by: input.Element), 81 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 83 82 ) 84 83 85 84 test_inputs( ··· 92 91 disabled: False, 93 92 required: True, 94 93 value: "val", 95 - args: WidgetArgs("id", labelled_by: input.Element), 94 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 96 95 ) 97 96 98 97 test_inputs( ··· 105 104 disabled: False, 106 105 required: True, 107 106 value: "", 108 - args: WidgetArgs("id", labelled_by: input.Value), 107 + args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 109 108 ) 110 109 } 111 110 ··· 120 119 disabled: False, 121 120 required: True, 122 121 value: "", 123 - args: WidgetArgs("id", labelled_by: input.Id("div")), 122 + args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 124 123 ) 125 124 test_inputs( 126 125 string_widgets.checkbox_widget(), ··· 132 131 disabled: False, 133 132 required: True, 134 133 value: "on", 135 - args: WidgetArgs("id", labelled_by: input.Value), 134 + args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 136 135 ) 137 136 138 137 test_inputs( ··· 145 144 disabled: False, 146 145 required: True, 147 146 value: "on", 148 - args: WidgetArgs("id", labelled_by: input.Element), 147 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 149 148 ) 150 149 } 151 150 ··· 160 159 disabled: False, 161 160 required: True, 162 161 value: "", 163 - args: WidgetArgs("id", labelled_by: input.Id("div")), 162 + args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 164 163 ) 165 164 test_inputs( 166 165 string_widgets.password_widget(), ··· 172 171 disabled: False, 173 172 required: True, 174 173 value: "xxxx", 175 - args: WidgetArgs("id", labelled_by: input.Value), 174 + args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 176 175 ) 177 176 178 177 test_inputs( ··· 185 184 disabled: False, 186 185 required: True, 187 186 value: "xxxx", 188 - args: WidgetArgs("id", labelled_by: input.Element), 187 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 189 188 ) 190 189 } 191 190 ··· 200 199 disabled: False, 201 200 required: True, 202 201 value: "", 203 - args: WidgetArgs("id", labelled_by: input.Id("div")), 202 + args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 204 203 ) 205 204 test_inputs( 206 205 string_widgets.textarea_widget(), ··· 212 211 disabled: False, 213 212 required: True, 214 213 value: "1", 215 - args: WidgetArgs("id", labelled_by: input.Value), 214 + args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 216 215 ) 217 216 218 217 test_inputs( ··· 225 224 disabled: False, 226 225 required: True, 227 226 value: "1", 228 - args: WidgetArgs("id", labelled_by: input.Element), 227 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 229 228 ) 230 229 } 231 230 ··· 240 239 disabled: False, 241 240 required: True, 242 241 value: "", 243 - args: WidgetArgs("id", labelled_by: input.Id("div")), 242 + args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 244 243 ) 245 244 test_inputs( 246 245 string_widgets.hidden_widget(), ··· 252 251 disabled: False, 253 252 required: True, 254 253 value: "1", 255 - args: WidgetArgs("id", labelled_by: input.Value), 254 + args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 256 255 ) 257 256 258 257 test_inputs( ··· 265 264 disabled: False, 266 265 required: True, 267 266 value: "1", 268 - args: WidgetArgs("id", labelled_by: input.Element), 267 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 269 268 ) 270 269 } 271 270 ··· 281 280 disabled: False, 282 281 required: True, 283 282 value: "", 284 - args: WidgetArgs("id", labelled_by: input.Id("div")), 283 + args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 285 284 ) 286 285 test_inputs( 287 286 string_widgets.select_widget(list), ··· 293 292 disabled: False, 294 293 required: True, 295 294 value: "1", 296 - args: WidgetArgs("id", labelled_by: input.Value), 295 + args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 297 296 ) 298 297 299 298 test_inputs( ··· 306 305 disabled: False, 307 306 required: True, 308 307 value: "1", 309 - args: WidgetArgs("id", labelled_by: input.Element), 308 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 310 309 ) 311 310 }
+1 -1
formz_nakai/src/formz_nakai/fields.gleam formz_nakai/src/formz_nakai/definitions.gleam
··· 1 - import formz/field.{Definition} 1 + import formz/definition.{Definition} 2 2 import formz/validation 3 3 import formz_nakai/widgets 4 4 import gleam/list
+7 -6
formz_nakai/src/formz_nakai/simple.gleam
··· 1 - import formz/field.{WidgetArgs} 1 + import formz/field 2 2 import formz/formz_use as formz 3 + import formz/widget 3 4 import gleam/list 4 5 import gleam/string 5 6 import nakai/attr ··· 7 8 8 9 pub fn generate_form(form) -> html.Node { 9 10 form 10 - |> formz.get_items 11 + |> formz.items 11 12 |> list.map(generate_visible_item) 12 13 |> html.Fragment() 13 14 } 14 15 15 16 pub fn generate_visible_item(item: formz.FormItem(html.Node)) -> html.Node { 16 17 case item { 17 - formz.Item(f) if f.hidden == True -> 18 + formz.Element(f, _) if f.hidden == True -> 18 19 html.input([attr.type_("hidden"), attr.name(f.name), attr.value(f.value)]) 19 - formz.Item(f) -> { 20 + formz.Element(f, make_widget) -> { 20 21 let label_el = 21 22 html.label([attr.for(f.name)], [html.Text(f.label), html.Text(": ")]) 22 23 ··· 26 27 } 27 28 let widget_el = 28 29 html.span([attr.class("widget")], [ 29 - field.run_widget( 30 + make_widget( 30 31 f, 31 - WidgetArgs(id: f.name, labelled_by: field.Element), 32 + widget.Args(id: f.name, labelled_by: widget.LabelledByLabelFor), 32 33 ), 33 34 ]) 34 35
+19 -17
formz_nakai/src/formz_nakai/widgets.gleam
··· 1 - import formz/field.{type Field, type WidgetArgs} 1 + import formz/field.{type Field} 2 + import formz/widget 3 + 2 4 import gleam/list 3 5 import gleam/string 4 6 import nakai/attr ··· 19 21 } 20 22 21 23 fn aria_label_attr( 22 - labelled_by: field.InputLabelled, 24 + labelled_by: widget.LabelledBy, 23 25 label: String, 24 26 ) -> List(attr.Attr) { 25 27 case labelled_by { 26 - field.Element -> [] 27 - field.Id(id) -> [attr.aria_labelledby(id)] 28 - field.Value -> 28 + widget.LabelledByLabelFor -> [] 29 + widget.LabelledByElementWithId(id) -> [attr.aria_labelledby(id)] 30 + widget.LabelledByFieldValue -> 29 31 case label { 30 32 "" -> [] 31 33 _ -> [attr.aria_label(label)] ··· 45 47 } 46 48 47 49 pub fn checkbox_widget() { 48 - field.widget(fn(field: Field(html.Node), args: field.WidgetArgs) -> html.Node { 50 + fn(field: Field, args: widget.Args) -> html.Node { 49 51 let checked_attr = case field.value { 50 52 "on" -> [attr.checked()] 51 53 _ -> [] ··· 60 62 aria_label_attr(args.labelled_by, field.label), 61 63 ]), 62 64 ) 63 - }) 65 + } 64 66 } 65 67 66 68 pub fn password_widget() { 67 - field.widget(fn(field: Field(html.Node), args: WidgetArgs) -> html.Node { 69 + fn(field: Field, args: widget.Args) -> html.Node { 68 70 html.input( 69 71 list.flatten([ 70 72 type_attr("password"), ··· 74 76 aria_label_attr(args.labelled_by, field.label), 75 77 ]), 76 78 ) 77 - }) 79 + } 78 80 } 79 81 80 82 pub fn text_like_widget(type_: String) { 81 - field.widget(fn(field: Field(html.Node), args: WidgetArgs) -> html.Node { 83 + fn(field: Field, args: widget.Args) -> html.Node { 82 84 html.input( 83 85 list.flatten([ 84 86 type_attr(type_), ··· 88 90 aria_label_attr(args.labelled_by, field.label), 89 91 ]), 90 92 ) 91 - }) 93 + } 92 94 } 93 95 94 96 pub fn textarea_widget() { 95 - field.widget(fn(field: Field(html.Node), args: WidgetArgs) -> html.Node { 97 + fn(field: Field, args: widget.Args) -> html.Node { 96 98 html.textarea( 97 99 list.flatten([ 98 100 name_attr(field.name), ··· 101 103 ]), 102 104 [html.Text(field.value)], 103 105 ) 104 - }) 106 + } 105 107 } 106 108 107 109 pub fn hidden_widget() { 108 - field.widget(fn(field: Field(html.Node), _) -> html.Node { 110 + fn(field: Field, _) -> html.Node { 109 111 html.input( 110 112 list.flatten([ 111 113 type_attr("hidden"), ··· 113 115 value_attr(field.value), 114 116 ]), 115 117 ) 116 - }) 118 + } 117 119 } 118 120 119 121 pub fn select_widget(variants: List(#(String, value))) { 120 - field.widget(fn(field: Field(html.Node), args: WidgetArgs) -> html.Node { 122 + fn(field: Field, args: widget.Args) -> html.Node { 121 123 html.select( 122 124 list.flatten([ 123 125 name_attr(field.name), ··· 138 140 }), 139 141 ]), 140 142 ) 141 - }) 143 + } 142 144 }
+19 -20
formz_nakai/test/formz_nakai/fields_test.gleam
··· 1 - import formz/field 1 + import formz/definition 2 2 import gleeunit 3 3 import gleeunit/should 4 4 5 - import formz_nakai/fields 6 - import formz_string/fields as string_fields 5 + import formz_nakai/definitions 6 + import formz_string/definitions as string_kinds 7 7 8 8 pub fn main() { 9 9 gleeunit.main() 10 10 } 11 11 12 12 fn fields_should_be_equal_except_widget( 13 - field1: field.Definition(format1, output), 14 - field2: field.Definition(format2, output), 13 + field1: definition.Definition(format1, output), 14 + field2: definition.Definition(format2, output), 15 15 ) { 16 16 field1.placeholder |> should.equal(field2.placeholder) 17 17 field1.transform |> should.equal(field2.transform) 18 18 } 19 19 20 20 pub fn text_field_test() { 21 - let string_field = string_fields.text_field() 22 - let field = fields.text_field() 21 + let string_field = string_kinds.text_field() 22 + let field = definitions.text_field() 23 23 24 24 fields_should_be_equal_except_widget(field, string_field) 25 25 } 26 26 27 27 pub fn email_field_test() { 28 - let string_field = string_fields.email_field() 29 - let field = fields.email_field() 28 + let string_field = string_kinds.email_field() 29 + let field = definitions.email_field() 30 30 31 31 fields_should_be_equal_except_widget(field, string_field) 32 32 } 33 33 34 34 pub fn number_field_test() { 35 - let string_field = string_fields.number_field() 36 - let field = fields.number_field() 35 + let string_field = string_kinds.number_field() 36 + let field = definitions.number_field() 37 37 38 38 fields_should_be_equal_except_widget(field, string_field) 39 39 } 40 40 41 41 pub fn integer_field_test() { 42 - let string_field = string_fields.integer_field() 43 - let field = fields.integer_field() 42 + let string_field = string_kinds.integer_field() 43 + let field = definitions.integer_field() 44 44 45 45 fields_should_be_equal_except_widget(field, string_field) 46 46 } 47 47 48 48 pub fn boolean_field_test() { 49 - let string_field = string_fields.boolean_field() 50 - let field = fields.boolean_field() 49 + let string_field = string_kinds.boolean_field() 50 + let field = definitions.boolean_field() 51 51 52 52 fields_should_be_equal_except_widget(field, string_field) 53 53 } 54 54 55 55 pub fn enum_field_test() { 56 - let string_field = string_fields.enum_field([#("a", "A"), #("b", "B")]) 57 - let field = fields.enum_field([#("a", "A"), #("b", "B")]) 56 + let string_field = string_kinds.enum_field([#("a", "A"), #("b", "B")]) 57 + let field = definitions.enum_field([#("a", "A"), #("b", "B")]) 58 58 59 59 fields_should_be_equal_except_widget(field, string_field) 60 60 } 61 61 62 62 pub fn indexed_enum_field_test() { 63 - let string_field = 64 - string_fields.indexed_enum_field([#("a", "A"), #("b", "B")]) 65 - let field = fields.indexed_enum_field([#("a", "A"), #("b", "B")]) 63 + let string_field = string_kinds.indexed_enum_field([#("a", "A"), #("b", "B")]) 64 + let field = definitions.indexed_enum_field([#("a", "A"), #("b", "B")]) 66 65 67 66 fields_should_be_equal_except_widget(field, string_field) 68 67 }
+27 -28
formz_nakai/test/formz_nakai/widgets_test.gleam
··· 1 - import formz/input.{type WidgetArgs, WidgetArgs} 1 + import formz/field 2 + import formz/widget 2 3 import gleam/string 3 4 import gleeunit 4 5 import gleeunit/should ··· 43 44 string string_widget, 44 45 widget widget, 45 46 ) { 46 - let string_input = 47 - input.Valid( 47 + let string_field = 48 + field.Valid( 48 49 name:, 49 50 label:, 50 51 help_text:, 51 - widget: string_widget, 52 52 hidden:, 53 53 value:, 54 54 disabled:, 55 55 required:, 56 56 ) 57 - let input = 58 - input.Valid( 57 + let field = 58 + field.Valid( 59 59 name:, 60 60 label:, 61 61 help_text:, 62 - widget: widget, 63 62 hidden:, 64 63 value:, 65 64 disabled:, 66 65 required:, 67 66 ) 68 67 69 - input.widget(input, args) 68 + widget(field, args) 70 69 |> convert_to_string 71 - |> should.equal(string_input.widget(string_input, args)) 70 + |> should.equal(string_widget(string_field, args)) 72 71 } 73 72 74 73 pub fn text_widget_test() { ··· 82 81 disabled: False, 83 82 required: True, 84 83 value: "", 85 - args: WidgetArgs("id", labelled_by: input.Element), 84 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 86 85 ) 87 86 test_inputs( 88 87 string_widgets.text_like_widget("text"), ··· 94 93 disabled: False, 95 94 required: True, 96 95 value: "", 97 - args: WidgetArgs("id", labelled_by: input.Element), 96 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 98 97 ) 99 98 100 99 test_inputs( ··· 107 106 disabled: False, 108 107 required: True, 109 108 value: "val", 110 - args: WidgetArgs("id", labelled_by: input.Element), 109 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 111 110 ) 112 111 113 112 test_inputs( ··· 120 119 disabled: False, 121 120 required: True, 122 121 value: "", 123 - args: WidgetArgs("id", labelled_by: input.Value), 122 + args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 124 123 ) 125 124 } 126 125 ··· 135 134 disabled: False, 136 135 required: True, 137 136 value: "", 138 - args: WidgetArgs("id", labelled_by: input.Id("div")), 137 + args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 139 138 ) 140 139 test_inputs( 141 140 string_widgets.checkbox_widget(), ··· 147 146 disabled: False, 148 147 required: True, 149 148 value: "on", 150 - args: WidgetArgs("id", labelled_by: input.Value), 149 + args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 151 150 ) 152 151 153 152 test_inputs( ··· 160 159 disabled: False, 161 160 required: True, 162 161 value: "on", 163 - args: WidgetArgs("id", labelled_by: input.Element), 162 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 164 163 ) 165 164 } 166 165 ··· 175 174 disabled: False, 176 175 required: True, 177 176 value: "", 178 - args: WidgetArgs("id", labelled_by: input.Id("div")), 177 + args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 179 178 ) 180 179 test_inputs( 181 180 string_widgets.password_widget(), ··· 187 186 disabled: False, 188 187 required: True, 189 188 value: "xxxx", 190 - args: WidgetArgs("id", labelled_by: input.Value), 189 + args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 191 190 ) 192 191 193 192 test_inputs( ··· 200 199 disabled: False, 201 200 required: True, 202 201 value: "xxxx", 203 - args: WidgetArgs("id", labelled_by: input.Element), 202 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 204 203 ) 205 204 } 206 205 ··· 215 214 disabled: False, 216 215 required: True, 217 216 value: "", 218 - args: WidgetArgs("id", labelled_by: input.Id("div")), 217 + args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 219 218 ) 220 219 test_inputs( 221 220 string_widgets.textarea_widget(), ··· 227 226 disabled: False, 228 227 required: True, 229 228 value: "1", 230 - args: WidgetArgs("id", labelled_by: input.Value), 229 + args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 231 230 ) 232 231 233 232 test_inputs( ··· 240 239 disabled: False, 241 240 required: True, 242 241 value: "1", 243 - args: WidgetArgs("id", labelled_by: input.Element), 242 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 244 243 ) 245 244 } 246 245 ··· 255 254 disabled: False, 256 255 required: True, 257 256 value: "", 258 - args: WidgetArgs("id", labelled_by: input.Id("div")), 257 + args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 259 258 ) 260 259 test_inputs( 261 260 string_widgets.hidden_widget(), ··· 267 266 disabled: False, 268 267 required: True, 269 268 value: "1", 270 - args: WidgetArgs("id", labelled_by: input.Value), 269 + args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 271 270 ) 272 271 273 272 test_inputs( ··· 280 279 disabled: False, 281 280 required: True, 282 281 value: "1", 283 - args: WidgetArgs("id", labelled_by: input.Element), 282 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 284 283 ) 285 284 } 286 285 ··· 296 295 disabled: False, 297 296 required: True, 298 297 value: "", 299 - args: WidgetArgs("id", labelled_by: input.Id("div")), 298 + args: widget.Args("id", labelled_by: widget.LabelledByElementWithId("div")), 300 299 ) 301 300 test_inputs( 302 301 string_widgets.select_widget(list), ··· 308 307 disabled: False, 309 308 required: True, 310 309 value: "1", 311 - args: WidgetArgs("id", labelled_by: input.Value), 310 + args: widget.Args("id", labelled_by: widget.LabelledByFieldValue), 312 311 ) 313 312 314 313 test_inputs( ··· 321 320 disabled: False, 322 321 required: True, 323 322 value: "1", 324 - args: WidgetArgs("id", labelled_by: input.Element), 323 + args: widget.Args("id", labelled_by: widget.LabelledByLabelFor), 325 324 ) 326 325 }
+1 -1
formz_string/src/formz_string/fields.gleam formz_string/src/formz_string/definitions.gleam
··· 1 - import formz/field.{Definition} 1 + import formz/definition.{Definition} 2 2 import formz/validation 3 3 import formz_string/widgets 4 4 import gleam/list
+6 -6
formz_string/src/formz_string/simple.gleam
··· 1 1 import formz/field 2 2 import formz/formz_use as formz 3 - import formz_string/widgets 3 + import formz/widget 4 4 import gleam/list 5 5 import gleam/string 6 6 7 7 pub fn generate_form(form) -> String { 8 8 { 9 9 form 10 - |> formz.get_items 10 + |> formz.items 11 11 |> list.map(generate_item) 12 12 |> string.join("\n") 13 13 } ··· 15 15 16 16 pub fn generate_item(item: formz.FormItem(String)) -> String { 17 17 case item { 18 - formz.Item(f) if f.hidden == True -> 18 + formz.Element(f, _) if f.hidden == True -> 19 19 "<input" 20 20 <> { " type=\"hidden\"" } 21 21 <> { " name=\"" <> f.name <> "\"" } 22 22 <> { " value\"" <> f.value <> "\"" } 23 23 <> ">" 24 - formz.Item(f) -> 24 + formz.Element(f, widget) -> 25 25 case f.hidden { 26 26 True -> "" 27 27 False -> { ··· 29 29 "<label for=\"" <> f.name <> "\">" <> f.label <> ": </label>" 30 30 let description_el = case string.is_empty(f.help_text) { 31 31 True -> "" 32 - False -> "<span class=\"description\">" <> f.help_text <> "</span>" 32 + False -> "<span class=\"description\">" <> f.help_text <> " </span>" 33 33 } 34 34 let widget_el = 35 35 "<span class=\"widget\">" 36 - <> field.run_widget(f, field.WidgetArgs(f.name, field.Element)) 36 + <> widget(f, widget.Args(f.name, widget.LabelledByLabelFor)) 37 37 <> "</span>" 38 38 39 39 let errors_el = case f {
+18 -17
formz_string/src/formz_string/widgets.gleam
··· 1 - import formz/field.{type Field, type WidgetArgs} 1 + import formz/field.{type Field} 2 + import formz/widget 2 3 import gleam/list 3 4 import gleam/string 4 5 ··· 16 17 } 17 18 } 18 19 19 - fn aria_label_attr(labelled_by: field.InputLabelled, label: String) -> String { 20 + fn aria_label_attr(labelled_by: widget.LabelledBy, label: String) -> String { 20 21 case labelled_by { 21 22 // there should be a label with a for attribute pointing to this id 22 - field.Element -> "" 23 + widget.LabelledByLabelFor -> "" 23 24 24 25 // we have the id of the element that labels this input 25 - field.Id(id) -> " aria-labelledby=\"" <> id <> "\"" 26 + widget.LabelledByElementWithId(id) -> " aria-labelledby=\"" <> id <> "\"" 26 27 27 28 // we'll use the label value as the aria-label 28 - field.Value -> { 29 + widget.LabelledByFieldValue -> { 29 30 let sanitized_label = 30 31 label 31 32 |> string.replace("\"", "&quot;") ··· 54 55 } 55 56 56 57 pub fn checkbox_widget() { 57 - field.widget(fn(field: Field(String), args: WidgetArgs) -> String { 58 + fn(field: Field, args: widget.Args) -> String { 58 59 let checked_attr = case field.value { 59 60 "on" -> " checked" 60 61 _ -> "" ··· 67 68 <> checked_attr 68 69 <> aria_label_attr(args.labelled_by, field.label) 69 70 <> ">" 70 - }) 71 + } 71 72 } 72 73 73 74 pub fn password_widget() { 74 - field.widget(fn(field: Field(String), args: WidgetArgs) -> String { 75 + fn(field: Field, args: widget.Args) -> String { 75 76 "<input" 76 77 <> type_attr("password") 77 78 <> name_attr(field.name) ··· 79 80 // <> value_attr(field.value) 80 81 <> aria_label_attr(args.labelled_by, field.label) 81 82 <> ">" 82 - }) 83 + } 83 84 } 84 85 85 86 pub fn text_like_widget(type_: String) { 86 - field.widget(fn(field: Field(String), args: WidgetArgs) -> String { 87 + fn(field: Field, args: widget.Args) -> String { 87 88 "<input" 88 89 <> type_attr(type_) 89 90 <> name_attr(field.name) ··· 91 92 <> value_attr(field.value) 92 93 <> aria_label_attr(args.labelled_by, field.label) 93 94 <> ">" 94 - }) 95 + } 95 96 } 96 97 97 98 pub fn textarea_widget() { 98 - field.widget(fn(field: Field(String), args: WidgetArgs) -> String { 99 + fn(field: Field, args: widget.Args) -> String { 99 100 // https://chriscoyier.net/2023/09/29/css-solves-auto-expanding-textareas-probably-eventually/ 100 101 // https://til.simonwillison.net/css/resizing-textarea 101 102 "<textarea" ··· 105 106 <> ">" 106 107 <> field.value 107 108 <> "</textarea>" 108 - }) 109 + } 109 110 } 110 111 111 112 pub fn hidden_widget() { 112 - field.widget(fn(field: Field(String), _args: WidgetArgs) -> String { 113 + fn(field: Field, _args: widget.Args) -> String { 113 114 "<input" 114 115 <> type_attr("hidden") 115 116 <> name_attr(field.name) 116 117 <> value_attr(field.value) 117 118 <> ">" 118 - }) 119 + } 119 120 } 120 121 121 122 pub fn select_widget(variants: List(#(String, value))) { 122 - field.widget(fn(field: Field(String), args: WidgetArgs) { 123 + fn(field: Field, args: widget.Args) { 123 124 let choices = 124 125 list.map(variants, fn(variant) { 125 126 let val = string.inspect(variant.1) ··· 146 147 <> { "<hr>" } 147 148 <> choices 148 149 <> { "</select>" } 149 - }) 150 + } 150 151 }