this repo has no description
4
fork

Configure Feed

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

add select… option, bunch of api cleanup

+1106 -506
+37 -102
formz/src/formz/field.gleam
··· 1 - import formz/input.{type Input, Input} 2 - import formz/validation 3 - import gleam/list 1 + import formz/input.{type Input} 4 2 import gleam/option 5 - import gleam/pair 6 3 import gleam/result 7 4 import gleam/string 8 5 import justin 9 6 10 - pub fn text_field( 11 - widget: fn(Input(format), input.WidgetArgs) -> format, 12 - ) -> Field(format, String) { 13 - Field(input.empty_field(widget), "", validation.string) 14 - } 15 - 16 - pub fn email_field( 17 - widget: fn(Input(format), input.WidgetArgs) -> format, 18 - ) -> Field(format, String) { 19 - Field(input.empty_field(widget), "", validation.email) 20 - } 21 - 22 - pub fn integer_field( 23 - widget: fn(Input(format), input.WidgetArgs) -> format, 24 - ) -> Field(format, Int) { 25 - let transform = validation.int 26 - Field(input.empty_field(widget), 0, transform) 27 - } 28 - 29 - pub fn number_field( 30 - widget: fn(Input(format), input.WidgetArgs) -> format, 31 - ) -> Field(format, Float) { 32 - let transform = validation.number 33 - Field(input.empty_field(widget), 0.0, transform) 34 - } 35 - 36 - pub fn boolean_field( 37 - widget: fn(Input(format), input.WidgetArgs) -> format, 38 - ) -> Field(format, Bool) { 39 - let transform = validation.boolean 40 - Field(input.empty_field(widget), False, transform) 41 - } 42 - 43 - pub fn enum_field( 44 - variants: List(#(String, enum)), 45 - widget: fn(Input(format), input.WidgetArgs) -> format, 46 - ) -> Field(format, enum) { 47 - let transform = validation.enum(variants) 48 - // todo should i force this to be a non empty list? 49 - // https://github.com/giacomocavalieri/non_empty_list 50 - // on the one hand it needs to be non empty, on the other 51 - // hand it's an unfamiliar type to most gleam users 52 - let assert Ok(first) = list.first(variants) 53 - Field(input.empty_field(widget), pair.second(first), transform) 54 - } 55 - 56 - pub fn indexed_enum_field( 57 - variants: List(#(String, enum)), 58 - widget: fn(Input(format), input.WidgetArgs) -> format, 59 - ) -> Field(format, enum) { 60 - let transform = validation.enum_by_index(variants) 61 - // todo should i force this to be a non empty list? 62 - // https://github.com/giacomocavalieri/non_empty_list 63 - // on the one hand it needs to be non empty, on the other 64 - // hand it's an unfamiliar type to most gleam users 65 - let assert Ok(first) = list.first(variants) 66 - Field(input.empty_field(widget), pair.second(first), transform) 67 - } 68 - 69 7 pub type Field(format, output) { 70 8 Field( 71 9 input: Input(format), 72 - default: output, 10 + placeholder: output, 73 11 transform: fn(String) -> Result(output, String), 74 12 ) 75 13 } 76 14 77 - pub fn field( 78 - name: String, 79 - field: Field(format, output), 80 - ) -> Field(format, output) { 81 - let label = case field.input.name { 82 - "" -> justin.sentence_case(name) 83 - _ -> field.input.name 84 - } 85 - field |> set_name(name) |> set_label(label) 86 - } 87 - 88 - pub fn hidden( 89 - name: String, 90 - field: Field(format, output), 91 - ) -> Field(format, output) { 92 - Field( 93 - Input(name, "", "", field.input.widget, True, ""), 94 - field.default, 95 - field.transform, 15 + pub type Definition(format, output) { 16 + Definition( 17 + widget: input.Widget(format), 18 + transform: fn(String) -> Result(output, String), 19 + placeholder: output, 96 20 ) 97 21 } 98 22 99 - pub fn full( 23 + pub fn field( 100 24 name: String, 101 - label: String, 102 - help_text: String, 103 - field: Field(format, output), 25 + definition: Definition(format, output), 104 26 ) -> Field(format, output) { 105 27 Field( 106 - Input( 28 + input.Valid( 107 29 name, 108 - label, 109 - help_text, 110 - field.input.widget, 111 - field.input.hidden, 112 - field.input.value, 30 + justin.sentence_case(name), 31 + help_text: "", 32 + widget: definition.widget, 33 + hidden: False, 34 + value: "", 35 + disabled: False, 36 + required: True, 113 37 ), 114 - field.default, 115 - field.transform, 38 + definition.placeholder, 39 + definition.transform, 116 40 ) 117 41 } 118 42 ··· 142 66 Field(..field, input: input.set_value(field.input, value)) 143 67 } 144 68 145 - pub fn set_hidden(field: Field(format, b), hidden: Bool) -> Field(format, b) { 146 - Field(..field, input: input.set_hidden(field.input, hidden)) 69 + pub fn set_visibility( 70 + field: Field(format, b), 71 + visibility: Bool, 72 + ) -> Field(format, b) { 73 + Field(..field, input: input.set_hidden(field.input, visibility)) 74 + } 75 + 76 + pub fn make_visible(field: Field(format, b)) -> Field(format, b) { 77 + set_visibility(field, True) 78 + } 79 + 80 + pub fn make_hidden(field: Field(format, b)) -> Field(format, b) { 81 + set_visibility(field, False) 147 82 } 148 83 149 84 pub fn set_optional(field: Field(format, b)) -> Field(format, option.Option(b)) { 150 - Field(input: field.input, default: option.None, transform: fn(str) { 85 + Field(input: field.input, placeholder: option.None, transform: fn(str) { 151 86 case string.trim(str) { 152 87 "" -> Ok(option.None) 153 88 _ -> result.map(field.transform(str), option.Some) ··· 159 94 field: Field(format, b), 160 95 next: fn(b) -> Result(b, String), 161 96 ) -> Field(format, b) { 162 - let Field(field, default, previous_transform) = field 97 + let Field(field, placeholder, previous_transform) = field 163 98 164 - Field(field, default, fn(str) { 99 + Field(field, placeholder, fn(str) { 165 100 case previous_transform(str) { 166 101 Ok(value) -> next(value) 167 102 Error(error) -> Error(error) ··· 171 106 172 107 pub fn transforms( 173 108 field: Field(format, b), 174 - default: c, 109 + placeholder: c, 175 110 next: fn(b) -> Result(c, String), 176 111 ) -> Field(format, c) { 177 112 let Field(field, _, previous_transform) = field 178 113 179 - Field(field, default, fn(str) { 114 + Field(field, placeholder, fn(str) { 180 115 case previous_transform(str) { 181 116 Ok(value) -> next(value) 182 117 Error(error) -> Error(error)
+7 -4
formz/src/formz/formz_pipes.gleam
··· 6 6 // - form sets 7 7 // - decoders/toy.decoder? 8 8 // - csrf token 9 - // - overwrite error messages 10 9 // - emit warning on duplicate named fields 11 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? 12 15 13 16 import formz/field.{type Field} 14 - import formz/input.{type Input, Input} 17 + import formz/input.{type Input, Valid} 15 18 import gleam/list 16 19 import gleam/option.{type Option, None, Some} 17 20 import gleam/result ··· 105 108 // no more data! return all the fields we have left plus the ones we accumulated 106 109 _, [] -> list.append(fields, acc) 107 110 // we have a field and data, and the names match. update field to have data 108 - [Input(name: field_name, ..) as field, ..fields_rest], 111 + [Valid(name: field_name, ..) as field, ..fields_rest], 109 112 [#(data_name, value), ..data_rest] 110 113 if field_name == data_name 111 114 -> ··· 142 145 } 143 146 } 144 147 145 - pub fn parse_and_try( 148 + pub fn try( 146 149 form: Form(format, output, decoder, HasDecoder), 147 150 apply fun: fn(output, Form(format, output, decoder, HasDecoder)) -> 148 151 Result(c, Form(format, output, decoder, HasDecoder)),
+73 -40
formz/src/formz/formz_use.gleam
··· 1 - /////// field related functions 2 - 3 1 import formz/field.{type Field} 4 - import formz/input.{type Input, Input} 2 + import formz/input.{type Input} 5 3 import gleam/dict 6 4 import gleam/list 7 5 import gleam/result 8 6 9 7 pub opaque type Form(format, output) { 10 8 Form( 11 - inputs: List(FormItem(format)), 9 + items: List(FormItem(format)), 12 10 parse: fn(List(FormItem(format))) -> Result(output, List(FormItem(format))), 13 - default: output, 11 + placeholder: output, 14 12 ) 15 13 } 16 14 17 15 pub type FormItem(format) { 18 - Item(Input(format)) 19 - Fieldset(String, List(FormItem(format))) 16 + Item(input: Input(format)) 17 + Set(Fieldset(format), items: List(FormItem(format))) 18 + } 19 + 20 + pub type Fieldset(format) { 21 + Fieldset(prefix: String, label: String) 20 22 } 21 23 22 24 pub fn with( 23 25 field: Field(format, input_output), 24 26 fun: fn(input_output) -> Form(format, form_output), 25 27 ) -> Form(format, form_output) { 26 - // we pass in our default value, and we're going to throw away the 28 + // we pass in our placeholder value, and we're going to throw away the 27 29 // decoded result here, we just care about pulling out the fields 28 30 // from the form. 29 - let next = fun(field.default) 31 + let next = fun(field.placeholder) 30 32 31 33 // prepend the new input to the inputs from the form we got in the 32 34 // previous step. 33 - let updated_inputs = [Item(field.input), ..next.inputs] 35 + let updated_inputs = [Item(field.input), ..next.items] 34 36 35 37 // now create the parse function. parse function accepts most recent 36 38 // version of input list, since data can be added to it. the list ··· 43 45 let input_output = field.transform(input.value) 44 46 45 47 // pass our transformed input data to the next function/form. if 46 - // we errored we still do this with our default so we can continue 48 + // we errored we still do this with our placeholder so we can continue 47 49 // processing all the fields in the form. we will return a form 48 50 // with an error, so if we're on the error track we'll throw away 49 51 // the "output" made with this and just keep the errors. 50 - let next_form = fun(input_output |> result.unwrap(field.default)) 52 + let next_form = fun(input_output |> result.unwrap(field.placeholder)) 51 53 let form_output = next_form.parse(next_inputs) 52 54 53 55 // ok, check which track we're on ··· 79 81 |> Error 80 82 } 81 83 } 82 - Form(updated_inputs, parse: parse, default: next.default) 84 + Form(updated_inputs, parse: parse, placeholder: next.placeholder) 83 85 } 84 86 85 87 pub fn sub_form( ··· 88 90 sub: Form(format, sub_output), 89 91 fun: fn(sub_output) -> Form(format, form_output), 90 92 ) -> Form(format, form_output) { 91 - let next = fun(sub.default) 93 + let next = fun(sub.placeholder) 92 94 93 95 let sub_inputs = 94 - sub.inputs 96 + sub.items 95 97 |> map_items(fn(item) { item |> input.set_name(prefix <> "." <> item.name) }) 96 98 97 - let updated_inputs = [Fieldset(name, sub_inputs), ..next.inputs] 99 + let updated_inputs = [Set(Fieldset(prefix, name), sub_inputs), ..next.items] 98 100 99 101 let parse = fn(inputs: List(FormItem(format))) { 100 102 // pull out the latest version of this field to get latest input data 101 - let assert [Fieldset(name, items), ..next_inputs] = inputs 103 + let assert [Set(Fieldset(_, name), items), ..next_inputs] = inputs 102 104 103 105 let sub_output = sub.parse(items) 104 106 105 - let next_form = fun(sub_output |> result.unwrap(sub.default)) 107 + let next_form = fun(sub_output |> result.unwrap(sub.placeholder)) 106 108 let form_output = next_form.parse(next_inputs) 107 109 108 110 // ok, check which track we're on ··· 112 114 113 115 // form has errors, but this sub form was good, so add it to the list 114 116 // of items as is. 115 - Error(inputs), Ok(_value) -> Error([Fieldset(name, items), ..inputs]) 117 + Error(inputs), Ok(_value) -> 118 + Error([Set(Fieldset(prefix, name), items), ..inputs]) 116 119 117 120 // form was good so far, but this sub form errored, so need to 118 121 // hop on error track 119 122 Ok(_), Error(error_fields) -> 120 - Error([Fieldset(name, error_fields), ..next_inputs]) 123 + Error([Set(Fieldset(prefix, name), error_fields), ..next_inputs]) 121 124 122 125 // form already has errors and this form errored, so add this field 123 126 // to the list of errors 124 127 Error(fields), Error(error_fields) -> 125 - Error(list.prepend(fields, Fieldset(name, error_fields))) 128 + Error(list.prepend(fields, Set(Fieldset(prefix, name), error_fields))) 126 129 } 127 130 } 128 - Form(updated_inputs, parse: parse, default: next.default) 131 + Form(updated_inputs, parse: parse, placeholder: next.placeholder) 129 132 } 130 133 131 - pub fn next_item( 134 + fn next_item( 132 135 items: List(FormItem(format)), 133 136 ) -> Result(#(FormItem(format), List(FormItem(format))), Nil) { 134 137 case items { 135 138 [] -> Error(Nil) 136 139 [only] -> Ok(#(only, [])) 137 140 [Item(input), ..rest] -> Ok(#(Item(input), rest)) 138 - [Fieldset(_, []), ..rest] -> next_item(rest) 139 - [Fieldset(name, [first, ..rest_1]), ..rest_2] -> 140 - next_item(list.flatten([[first], [Fieldset(name, rest_1)], rest_2])) 141 + [Set(_, []), ..rest] -> next_item(rest) 142 + [Set(s, [first, ..rest_1]), ..rest_2] -> 143 + next_item(list.flatten([[first], [Set(s, rest_1)], rest_2])) 141 144 } 142 145 } 143 146 ··· 148 151 list.map(items, fn(item) { 149 152 case item { 150 153 Item(input) -> Item(fun(input)) 151 - Fieldset(name, items) -> Fieldset(name, map_items(items, fun)) 154 + Set(s, items) -> Set(s, map_items(items, fun)) 152 155 } 153 156 }) 154 157 } 155 158 156 - pub fn get_inputs(form: Form(format, ouput)) { 157 - form.inputs |> do_get_inputs([]) |> list.reverse 159 + fn get_inputs(form: Form(format, ouput)) { 160 + form.items |> do_get_inputs([]) |> list.reverse 158 161 } 159 162 160 163 fn do_get_inputs(items: List(FormItem(format)), acc) { 161 164 case items { 162 165 [] -> acc 163 166 [Item(input), ..rest] -> do_get_inputs(rest, [input, ..acc]) 164 - [Fieldset(_, items), ..rest] -> 165 - do_get_inputs(list.flatten([items, rest]), acc) 167 + [Set(_, items), ..rest] -> do_get_inputs(list.flatten([items, rest]), acc) 166 168 } 167 169 } 168 170 ··· 171 173 input_data: List(#(String, String)), 172 174 ) -> Form(format, output) { 173 175 let data = dict.from_list(input_data) 174 - let Form(inputs, parse, default) = form 176 + let Form(inputs, parse, placeholder) = form 175 177 inputs 176 178 |> map_items(fn(input) { 177 179 case dict.get(data, input.name) { ··· 179 181 Error(_) -> input 180 182 } 181 183 }) 182 - |> Form(parse, default) 184 + |> Form(parse, placeholder) 183 185 } 184 186 185 187 pub fn create_form(thing: thing) -> Form(format, thing) { ··· 189 191 pub fn parse(form: Form(format, output)) -> Result(output, Form(format, output)) { 190 192 // we've tagged that we have a decoder with out has_decoder phantom type 191 193 // so we can get away with let assert here 192 - let Form(fields, parse, default) = form 194 + let Form(fields, parse, placeholder) = form 193 195 case parse(fields) { 194 196 Ok(output) -> Ok(output) 195 - Error(fields) -> Error(Form(fields, parse, default)) 197 + Error(fields) -> Error(Form(fields, parse, placeholder)) 196 198 } 197 199 } 198 200 199 - pub fn parse_and_try( 201 + pub fn try( 200 202 form: Form(format, output), 201 203 apply fun: fn(output, Form(format, output)) -> Result(c, Form(format, output)), 202 204 ) -> Result(c, Form(format, output)) { 203 205 parse(form) |> result.try(fun(_, form)) 204 206 } 205 207 206 - pub fn get_items(form: Form(format, ouput)) -> List(FormItem(format)) { 207 - form.inputs 208 + pub fn get_items(form: Form(format, output)) -> List(FormItem(format)) { 209 + form.items 210 + } 211 + 212 + pub fn get_item( 213 + form: Form(format, output), 214 + prefix: String, 215 + ) -> Result(FormItem(format), Nil) { 216 + form.items 217 + |> list.filter(fn(item) { 218 + case item { 219 + Item(i) if i.name == prefix -> True 220 + Set(s, _) if s.prefix == prefix -> True 221 + _ -> False 222 + } 223 + }) 224 + |> list.first 225 + } 226 + 227 + pub fn map_item( 228 + form: Form(format, output), 229 + name: String, 230 + fun: fn(FormItem(format)) -> a, 231 + ) -> Result(a, Nil) { 232 + form |> get_item(name) |> result.map(fun) 208 233 } 209 234 210 235 pub fn get_input( ··· 217 242 |> list.first 218 243 } 219 244 245 + pub fn map_input( 246 + form: Form(format, output), 247 + name: String, 248 + fun: fn(Input(format)) -> a, 249 + ) -> Result(a, Nil) { 250 + form |> get_input(name) |> result.map(fun) 251 + } 252 + 220 253 pub fn update_input( 221 254 form: Form(format, output), 222 255 name: String, 223 256 fun: fn(Input(format)) -> Input(format), 224 257 ) -> Form(format, output) { 225 - form.inputs 258 + form.items 226 259 |> map_items(fn(field) { 227 260 case field.name == name { 228 261 True -> fun(field) 229 262 False -> field 230 263 } 231 264 }) 232 - |> Form(form.parse, form.default) 265 + |> Form(form.parse, form.placeholder) 233 266 }
+354 -40
formz/src/formz/input.gleam
··· 1 1 pub type Input(format) { 2 - Input( 2 + Valid( 3 3 name: String, 4 4 label: String, 5 5 help_text: String, 6 - widget: fn(Input(format), WidgetArgs) -> format, 6 + disabled: Bool, 7 + required: Bool, 8 + widget: Widget(format), 7 9 hidden: Bool, 8 10 value: String, 9 11 ) 10 - InvalidInput( 12 + Invalid( 11 13 name: String, 12 14 label: String, 13 15 help_text: String, 14 - widget: fn(Input(format), WidgetArgs) -> format, 16 + disabled: Bool, 17 + required: Bool, 18 + widget: Widget(format), 15 19 hidden: Bool, 16 20 value: String, 17 21 error: String, 18 22 ) 19 23 } 20 24 25 + pub type Widget(format) = 26 + fn(Input(format), WidgetArgs) -> format 27 + 21 28 pub type WidgetArgs { 22 29 WidgetArgs(id: String, labelled_by: InputLabelled) 23 30 } ··· 28 35 Id(element_id: String) 29 36 } 30 37 31 - pub fn empty_field(widget: fn(Input(format), _) -> format) -> Input(format) { 32 - Input("", "", "", widget, False, "") 33 - } 34 - 35 38 pub fn set_name(field: Input(format), name: String) -> Input(format) { 36 39 case field { 37 - Input(_, label, help_text, widget, hidden, value) -> 38 - Input(name, label, help_text, widget, hidden, value) 39 - InvalidInput(_, label, help_text, widget, hidden, value, error) -> 40 - InvalidInput(name, label, help_text, widget, hidden, value, error) 40 + Valid( 41 + _name, 42 + label:, 43 + help_text:, 44 + widget:, 45 + hidden:, 46 + value:, 47 + disabled:, 48 + required:, 49 + ) -> 50 + Valid( 51 + name:, 52 + label:, 53 + help_text:, 54 + widget:, 55 + hidden:, 56 + value:, 57 + disabled:, 58 + required:, 59 + ) 60 + Invalid( 61 + _name, 62 + label:, 63 + help_text:, 64 + widget:, 65 + hidden:, 66 + disabled:, 67 + required:, 68 + value:, 69 + error:, 70 + ) -> 71 + Invalid( 72 + name:, 73 + label:, 74 + help_text:, 75 + widget:, 76 + hidden:, 77 + value:, 78 + error:, 79 + disabled:, 80 + required:, 81 + ) 41 82 } 42 83 } 43 84 44 85 pub fn set_label(field: Input(format), label: String) -> Input(format) { 45 86 case field { 46 - Input(name, _, help_text, widget, hidden, value) -> 47 - Input(name, label, help_text, widget, hidden, value) 48 - InvalidInput(name, _, help_text, widget, hidden, value, error) -> 49 - InvalidInput(name, label, help_text, widget, hidden, value, error) 87 + Valid( 88 + _label, 89 + name:, 90 + help_text:, 91 + widget:, 92 + hidden:, 93 + value:, 94 + disabled:, 95 + required:, 96 + ) -> 97 + Valid( 98 + name:, 99 + label:, 100 + help_text:, 101 + widget:, 102 + hidden:, 103 + value:, 104 + disabled:, 105 + required:, 106 + ) 107 + Invalid( 108 + _label, 109 + name:, 110 + help_text:, 111 + widget:, 112 + hidden:, 113 + disabled:, 114 + required:, 115 + value:, 116 + error:, 117 + ) -> 118 + Invalid( 119 + name:, 120 + label:, 121 + help_text:, 122 + widget:, 123 + hidden:, 124 + value:, 125 + error:, 126 + disabled:, 127 + required:, 128 + ) 50 129 } 51 130 } 52 131 53 132 pub fn set_help_text(field: Input(format), help_text: String) -> Input(format) { 54 133 case field { 55 - Input(name, label, _, widget, hidden, value) -> 56 - Input(name, label, help_text, widget, hidden, value) 57 - InvalidInput(name, label, _, widget, hidden, value, error) -> 58 - InvalidInput(name, label, help_text, widget, hidden, value, error) 134 + Valid( 135 + _help_text, 136 + name:, 137 + label:, 138 + widget:, 139 + hidden:, 140 + value:, 141 + disabled:, 142 + required:, 143 + ) -> 144 + Valid( 145 + name:, 146 + label:, 147 + help_text:, 148 + widget:, 149 + hidden:, 150 + value:, 151 + disabled:, 152 + required:, 153 + ) 154 + Invalid( 155 + _help_text, 156 + name:, 157 + label:, 158 + widget:, 159 + hidden:, 160 + disabled:, 161 + required:, 162 + value:, 163 + error:, 164 + ) -> 165 + Invalid( 166 + name:, 167 + label:, 168 + help_text:, 169 + widget:, 170 + hidden:, 171 + value:, 172 + error:, 173 + disabled:, 174 + required:, 175 + ) 59 176 } 60 177 } 61 178 62 - pub fn set_widget( 63 - field: Input(format), 64 - widget: fn(Input(format), WidgetArgs) -> format, 65 - ) -> Input(format) { 179 + pub fn set_widget(field: Input(format), widget: Widget(format)) -> Input(format) { 66 180 case field { 67 - Input(name, label, help_text, _, hidden, value) -> 68 - Input(name, label, help_text, widget, hidden, value) 69 - InvalidInput(name, label, help_text, _, hidden, value, error) -> 70 - InvalidInput(name, label, help_text, widget, hidden, value, error) 181 + Valid( 182 + _widget, 183 + name:, 184 + label:, 185 + help_text:, 186 + hidden:, 187 + value:, 188 + disabled:, 189 + required:, 190 + ) -> 191 + Valid( 192 + name:, 193 + label:, 194 + help_text:, 195 + widget:, 196 + hidden:, 197 + value:, 198 + disabled:, 199 + required:, 200 + ) 201 + Invalid( 202 + _widget, 203 + name:, 204 + label:, 205 + help_text:, 206 + hidden:, 207 + disabled:, 208 + required:, 209 + value:, 210 + error:, 211 + ) -> 212 + Invalid( 213 + name:, 214 + label:, 215 + help_text:, 216 + widget:, 217 + hidden:, 218 + value:, 219 + error:, 220 + disabled:, 221 + required:, 222 + ) 71 223 } 72 224 } 73 225 74 226 pub fn set_hidden(field: Input(format), hidden: Bool) -> Input(format) { 75 227 case field { 76 - Input(name, label, help_text, widget, _, value) -> 77 - Input(name, label, help_text, widget, hidden, value) 78 - InvalidInput(name, label, help_text, widget, _, value, error) -> 79 - InvalidInput(name, label, help_text, widget, hidden, value, error) 228 + Valid( 229 + _hidden, 230 + name:, 231 + label:, 232 + help_text:, 233 + widget:, 234 + value:, 235 + disabled:, 236 + required:, 237 + ) -> 238 + Valid( 239 + name:, 240 + label:, 241 + help_text:, 242 + widget:, 243 + hidden:, 244 + value:, 245 + disabled:, 246 + required:, 247 + ) 248 + Invalid( 249 + _hidden, 250 + name:, 251 + label:, 252 + help_text:, 253 + widget:, 254 + disabled:, 255 + required:, 256 + value:, 257 + error:, 258 + ) -> 259 + Invalid( 260 + name:, 261 + label:, 262 + help_text:, 263 + widget:, 264 + hidden:, 265 + value:, 266 + error:, 267 + disabled:, 268 + required:, 269 + ) 270 + } 271 + } 272 + 273 + pub fn set_disabled(field: Input(format), disabled: Bool) -> Input(format) { 274 + case field { 275 + Valid( 276 + _disabled, 277 + name:, 278 + label:, 279 + help_text:, 280 + widget:, 281 + value:, 282 + hidden:, 283 + required:, 284 + ) -> 285 + Valid( 286 + name:, 287 + label:, 288 + help_text:, 289 + widget:, 290 + hidden:, 291 + value:, 292 + disabled:, 293 + required:, 294 + ) 295 + Invalid( 296 + _disabled, 297 + name:, 298 + label:, 299 + help_text:, 300 + widget:, 301 + hidden:, 302 + required:, 303 + value:, 304 + error:, 305 + ) -> 306 + Invalid( 307 + name:, 308 + label:, 309 + help_text:, 310 + widget:, 311 + hidden:, 312 + value:, 313 + error:, 314 + disabled:, 315 + required:, 316 + ) 80 317 } 81 318 } 82 319 83 320 pub fn set_value(field: Input(format), value: String) -> Input(format) { 84 321 case field { 85 - Input(name, label, help_text, widget, hidden, _) -> 86 - Input(name, label, help_text, widget, hidden, value) 87 - InvalidInput(name, label, help_text, widget, hidden, _, error) -> 88 - InvalidInput(name, label, help_text, widget, hidden, value, error) 322 + Valid( 323 + _value, 324 + name:, 325 + label:, 326 + help_text:, 327 + widget:, 328 + hidden:, 329 + disabled:, 330 + required:, 331 + ) -> 332 + Valid( 333 + name:, 334 + label:, 335 + help_text:, 336 + widget:, 337 + hidden:, 338 + value:, 339 + disabled:, 340 + required:, 341 + ) 342 + Invalid( 343 + _value, 344 + name:, 345 + label:, 346 + help_text:, 347 + widget:, 348 + hidden:, 349 + disabled:, 350 + required:, 351 + error:, 352 + ) -> 353 + Invalid( 354 + name:, 355 + label:, 356 + help_text:, 357 + widget:, 358 + hidden:, 359 + value:, 360 + error:, 361 + disabled:, 362 + required:, 363 + ) 89 364 } 90 365 } 91 366 92 367 pub fn set_error(field: Input(format), error: String) -> Input(format) { 93 368 case field { 94 - Input(name, label, help_text, widget, hidden, value) -> 95 - InvalidInput(name, label, help_text, widget, hidden, value, error) 96 - InvalidInput(name, label, help_text, widget, hidden, value, _) -> 97 - InvalidInput(name, label, help_text, widget, hidden, value, error) 369 + Valid( 370 + name:, 371 + label:, 372 + help_text:, 373 + widget:, 374 + hidden:, 375 + value:, 376 + disabled:, 377 + required:, 378 + ) -> 379 + Invalid( 380 + name:, 381 + label:, 382 + help_text:, 383 + widget:, 384 + hidden:, 385 + value:, 386 + disabled:, 387 + required:, 388 + error:, 389 + ) 390 + Invalid( 391 + _error, 392 + name:, 393 + label:, 394 + help_text:, 395 + widget:, 396 + hidden:, 397 + disabled:, 398 + required:, 399 + value:, 400 + ) -> 401 + Invalid( 402 + name:, 403 + label:, 404 + help_text:, 405 + widget:, 406 + hidden:, 407 + value:, 408 + error:, 409 + disabled:, 410 + required:, 411 + ) 98 412 } 99 413 }
-41
formz/src/formz/string_generator/fields.gleam
··· 1 - import formz/field 2 - import formz/string_generator/widgets 3 - import gleam/list 4 - 5 - pub fn text_field() { 6 - field.text_field(widgets.text_like_widget("text")) 7 - } 8 - 9 - pub fn email_field() { 10 - field.email_field(widgets.text_like_widget("email")) 11 - } 12 - 13 - pub fn integer_field() { 14 - field.integer_field(widgets.text_like_widget("number")) 15 - } 16 - 17 - pub fn number_field() { 18 - field.number_field(widgets.text_like_widget("number")) 19 - } 20 - 21 - pub fn boolean_field() { 22 - field.boolean_field(widgets.checkbox_widget()) 23 - } 24 - 25 - pub fn enum_field(variants: List(#(String, enum))) { 26 - field.enum_field(variants, widgets.select_widget(variants)) 27 - } 28 - 29 - pub fn hidden_field() { 30 - field.text_field(widgets.hidden_widget()) |> field.set_hidden(True) 31 - } 32 - 33 - pub fn indexed_enum_field(variants: List(#(String, enum))) { 34 - let keys_indexed = list.index_map(variants, fn(t, i) { #(t.0, i) }) 35 - field.indexed_enum_field(variants, widgets.select_widget(keys_indexed)) 36 - } 37 - 38 - pub fn list_field(variants: List(String)) { 39 - let tuple_list = list.map(variants, fn(s) { #(s, s) }) 40 - indexed_enum_field(tuple_list) 41 - }
+11 -20
formz/src/formz/string_generator/simple.gleam formz_string/src/formz_string/simple.gleam
··· 1 1 import formz/formz_use as formz 2 - import formz/input.{type Input, Input, InvalidInput} 3 - import formz/string_generator/widgets 2 + import formz/input 3 + import formz_string/widgets 4 4 import gleam/list 5 5 import gleam/string 6 6 ··· 8 8 { 9 9 form 10 10 |> formz.get_items 11 - |> list.map(generate_visible_item) 12 - |> string.join("\n") 13 - } 14 - <> { 15 - form 16 - |> formz.get_inputs 17 - |> list.filter(fn(f) { f.hidden }) 18 - |> list.map(generate_hidden_field) 11 + |> list.map(generate_item) 19 12 |> string.join("\n") 20 13 } 21 14 } 22 15 23 - pub fn generate_visible_item(item: formz.FormItem(String)) -> String { 16 + pub fn generate_item(item: formz.FormItem(String)) -> String { 24 17 case item { 18 + formz.Item(f) if f.hidden == True -> 19 + widgets.hidden_widget()(f, input.WidgetArgs("", input.Value)) 25 20 formz.Item(f) -> 26 21 case f.hidden { 27 22 True -> "" ··· 38 33 <> "</span>" 39 34 40 35 let errors_el = case f { 41 - Input(..) -> "<span class=\"error-placeholder\"></span>" 42 - InvalidInput(error:, ..) -> 36 + input.Valid(..) -> "<span class=\"error-placeholder\"></span>" 37 + input.Invalid(error:, ..) -> 43 38 "<span class=\"errors\">" <> error <> "</span>" 44 39 } 45 40 ··· 51 46 <> "</p>" 52 47 } 53 48 } 54 - formz.Fieldset(name, items) -> { 49 + formz.Set(s, items) -> { 55 50 "<fieldset><legend>" 56 - <> name 51 + <> s.label 57 52 <> "</legend>" 58 53 <> { 59 - list.map(items, generate_visible_item) 54 + list.map(items, generate_item) 60 55 |> string.join("\n") 61 56 } 62 57 <> "</fieldset>" 63 58 } 64 59 } 65 60 } 66 - 67 - pub fn generate_hidden_field(f: Input(String)) -> String { 68 - widgets.hidden_widget()(f, input.WidgetArgs("", input.Value)) 69 - }
formz/src/formz/string_generator/table.gleam formz_string/src/formz_string/table.gleam
+4
formz/src/formz/string_generator/widgets.gleam formz_string/src/formz_string/widgets.gleam
··· 140 140 <> aria_label_attr(args.labelled_by, input.label) 141 141 <> ">" 142 142 } 143 + // TODO make this placeholder option not selectable? with disabled selected attributes 144 + // https://stackoverflow.com/questions/5805059/how-do-i-make-a-placeholder-for-a-select-box 145 + <> { "<option value>Select...</option>" } 146 + <> { "<hr>" } 143 147 <> choices 144 148 <> { "</select>" } 145 149 }
+12 -8
formz/test/formz/formz_pipes_test.gleam
··· 197 197 fn should_be_field_no_error(field: input.Input(String)) { 198 198 should.equal( 199 199 field, 200 - input.Input( 200 + input.Valid( 201 201 name: field.name, 202 202 label: field.label, 203 203 help_text: field.help_text, 204 204 value: field.value, 205 205 widget: field.widget, 206 206 hidden: field.hidden, 207 + disabled: field.disabled, 208 + required: field.required, 207 209 ), 208 210 ) 209 211 } ··· 211 213 fn should_be_field_with_error(field: input.Input(String), str: String) { 212 214 should.equal( 213 215 field, 214 - input.InvalidInput( 216 + input.Invalid( 215 217 name: field.name, 216 218 label: field.label, 217 219 help_text: field.help_text, ··· 219 221 widget: field.widget, 220 222 hidden: field.hidden, 221 223 error: str, 224 + disabled: field.disabled, 225 + required: field.required, 222 226 ), 223 227 ) 224 228 } 225 229 226 - pub fn parse_and_try_test() { 230 + pub fn try_test() { 227 231 let f = 228 232 formz.new() 229 233 |> formz.add(field("a", fields.integer_field())) 230 234 |> formz.add(field("b", fields.integer_field())) 231 235 |> formz.add(field("c", fields.integer_field())) 232 - |> formz.decodes(fn(_) { fn(_) { fn(_) { 1 } } }) 236 + |> formz.decodes(fn(a) { fn(b) { fn(c) { [a, b, c] } } }) 233 237 |> formz.data([#("a", "1"), #("b", "2"), #("c", "3")]) 234 238 235 239 // can succeed 236 - formz.parse_and_try(f, fn(_, _) { Ok(3) }) 240 + formz.try(f, fn(_, _) { Ok(3) }) 237 241 |> should.equal(Ok(3)) 238 242 239 243 // can change type 240 - formz.parse_and_try(f, fn(_, _) { Ok("it worked") }) 244 + formz.try(f, fn(_, _) { Ok("it worked") }) 241 245 |> should.equal(Ok("it worked")) 242 246 243 247 // can error 244 - formz.parse_and_try(f, fn(_, form) { Error(form) }) 248 + formz.try(f, fn(_, form) { Error(form) }) 245 249 |> should.equal(Error(f)) 246 250 247 251 // can change field 248 252 let assert Error(form) = 249 - formz.parse_and_try(f, fn(_, form) { 253 + formz.try(f, fn(_, form) { 250 254 Error(formz.update_input(form, "a", input.set_error(_, "woops"))) 251 255 }) 252 256 let assert [fielda, fieldb, fieldc] = formz.get_inputs(form)
+26 -22
formz/test/formz/formz_use_test.gleam
··· 1 1 import formz/field.{field} 2 - import formz/formz_use as formz 2 + import formz/formz_use.{Item, Set} as formz 3 3 import formz/input 4 4 import formz/string_generator/fields 5 5 import formz/validation ··· 10 10 fn should_be_field_no_error(field: input.Input(String)) { 11 11 should.equal( 12 12 field, 13 - input.Input( 13 + input.Valid( 14 14 name: field.name, 15 15 label: field.label, 16 16 help_text: field.help_text, 17 17 value: field.value, 18 18 widget: field.widget, 19 19 hidden: field.hidden, 20 + disabled: field.disabled, 21 + required: field.required, 20 22 ), 21 23 ) 22 24 } ··· 24 26 fn should_be_field_with_error(field: input.Input(String), str: String) { 25 27 should.equal( 26 28 field, 27 - input.InvalidInput( 29 + input.Invalid( 28 30 name: field.name, 29 31 label: field.label, 30 32 help_text: field.help_text, ··· 32 34 widget: field.widget, 33 35 hidden: field.hidden, 34 36 error: str, 37 + disabled: field.disabled, 38 + required: field.required, 35 39 ), 36 40 ) 37 41 } ··· 80 84 81 85 pub fn empty_form_test() { 82 86 empty_form(1) 83 - |> formz.get_inputs 87 + |> formz.get_items 84 88 |> should.equal([]) 85 89 } 86 90 ··· 173 177 |> formz.data([#("first", "world")]) 174 178 |> formz.parse 175 179 176 - let assert [field] = formz.get_inputs(f) 180 + let assert [formz.Item(field)] = formz.get_items(f) 177 181 field |> should_be_field_with_error("Must be a whole number") 178 182 } 179 183 180 184 pub fn parse_triple_field_form_with_error_test() { 181 - let assert [fielda, fieldb, fieldc] = 185 + let assert [Item(fielda), Item(fieldb), Item(fieldc)] = 182 186 three_field_form() 183 187 |> formz.data([#("a", "xxxx"), #("b", "1"), #("c", "x")]) 184 188 |> formz.parse 185 189 |> get_form_from_error_result 186 - |> formz.get_inputs 190 + |> formz.get_items 187 191 188 192 fielda |> should_be_field_no_error 189 193 fieldb |> should_be_field_no_error 190 194 fieldc |> should_be_field_with_error("Must be a number") 191 195 192 - let assert [fielda, fieldb, fieldc] = 196 + let assert [Item(fielda), Item(fieldb), Item(fieldc)] = 193 197 three_field_form() 194 198 |> formz.data([#("a", "string"), #("b", "string"), #("c", "string")]) 195 199 |> formz.parse 196 200 |> get_form_from_error_result 197 - |> formz.get_inputs 201 + |> formz.get_items 198 202 fielda |> should_be_field_no_error 199 203 fieldb |> should_be_field_with_error("Must be a whole number") 200 204 fieldc |> should_be_field_with_error("Must be a number") 201 205 202 - let assert [fielda, fieldb, fieldc] = 206 + let assert [Item(fielda), Item(fieldb), Item(fieldc)] = 203 207 three_field_form() 204 208 |> formz.data([#("a", "string"), #("b", "string"), #("c", "3.4")]) 205 209 |> formz.parse 206 210 |> get_form_from_error_result 207 - |> formz.get_inputs 211 + |> formz.get_items 208 212 fielda |> should_be_field_no_error 209 213 fieldb |> should_be_field_with_error("Must be a whole number") 210 214 fieldc |> should_be_field_no_error 211 215 212 - let assert [fielda, fieldb, fieldc] = 216 + let assert [Item(fielda), Item(fieldb), Item(fieldc)] = 213 217 three_field_form() 214 218 |> formz.data([#("a", "."), #("b", "string"), #("c", "3.4")]) 215 219 |> formz.parse 216 220 |> get_form_from_error_result 217 - |> formz.get_inputs 221 + |> formz.get_items 218 222 fielda |> should_be_field_with_error("Must be longer than 3") 219 223 fieldb |> should_be_field_with_error("Must be a whole number") 220 224 fieldc |> should_be_field_no_error 221 225 222 - let assert [fielda, fieldb, fieldc] = 226 + let assert [Item(fielda), Item(fieldb), Item(fieldc)] = 223 227 three_field_form() 224 228 |> formz.data([#("a", "."), #("b", "1"), #("c", "3.4")]) 225 229 |> formz.parse 226 230 |> get_form_from_error_result 227 - |> formz.get_inputs 231 + |> formz.get_items 228 232 fielda |> should_be_field_with_error("Must be longer than 3") 229 233 fieldb |> should_be_field_no_error 230 234 fieldc |> should_be_field_no_error ··· 273 277 formz.create_form(#(a, b)) 274 278 } 275 279 276 - let assert [inputa, inputb, inputc, inputd] = 280 + let assert [Set(_, [Item(inputa), Item(inputb), Item(inputc)]), Item(inputd)] = 277 281 f2 278 282 |> formz.data([ 279 283 #("name.a", "a"), ··· 283 287 ]) 284 288 |> formz.parse 285 289 |> get_form_from_error_result 286 - |> formz.get_inputs 290 + |> formz.get_items 287 291 288 292 inputa |> should_be_field_with_error("Must be a whole number") 289 293 inputb |> should_be_field_no_error ··· 297 301 |> formz.data([#("a", "string"), #("b", "2"), #("c", "3.0")]) 298 302 299 303 // can succeed 300 - formz.parse_and_try(f, fn(_, _) { Ok(3) }) 304 + formz.try(f, fn(_, _) { Ok(3) }) 301 305 |> should.equal(Ok(3)) 302 306 303 307 // can change type 304 - formz.parse_and_try(f, fn(_, _) { Ok("it worked") }) 308 + formz.try(f, fn(_, _) { Ok("it worked") }) 305 309 |> should.equal(Ok("it worked")) 306 310 307 311 // can error 308 - formz.parse_and_try(f, fn(_, form) { Error(form) }) 312 + formz.try(f, fn(_, form) { Error(form) }) 309 313 |> should.equal(Error(f)) 310 314 311 315 // can change field 312 316 let assert Error(form) = 313 - formz.parse_and_try(f, fn(_, form) { 317 + formz.try(f, fn(_, form) { 314 318 form 315 319 |> formz.update_input("a", input.set_error(_, "woops")) 316 320 |> Error 317 321 }) 318 - let assert [fielda, fieldb, fieldc] = formz.get_inputs(form) 322 + let assert [Item(fielda), Item(fieldb), Item(fieldc)] = formz.get_items(form) 319 323 fielda |> should_be_field_with_error("woops") 320 324 fieldb |> should_be_field_no_error 321 325 fieldc |> should_be_field_no_error
+16 -3
formz_demo/src/formz_demo/example/page.gleam
··· 46 46 wisp.ok() |> wisp.html_body(html) 47 47 } 48 48 49 + fn get_inputs(form: formz.Form(format, ouput)) { 50 + form |> formz.get_items |> do_get_inputs([]) |> list.reverse 51 + } 52 + 53 + fn do_get_inputs(items: List(formz.FormItem(format)), acc) { 54 + case items { 55 + [] -> acc 56 + [formz.Item(input), ..rest] -> do_get_inputs(rest, [input, ..acc]) 57 + [formz.Set(_, items), ..rest] -> 58 + do_get_inputs(list.flatten([items, rest]), acc) 59 + } 60 + } 61 + 49 62 pub fn show_post( 50 63 input_data: option.Option(List(#(String, String))), 51 64 output: option.Option(a), ··· 56 69 option.Some(input_data) -> { 57 70 let fields_no_post = 58 71 form 59 - |> formz.get_inputs 72 + |> get_inputs 60 73 |> list.map(fn(i) { 61 74 html.tr([], [ 62 75 html.td([], [html.text(i.name)]), ··· 69 82 ]), 70 83 html.td([], [ 71 84 html.text(case i { 72 - input.Input(..) -> "" 73 - input.InvalidInput(error:, ..) -> error 85 + input.Valid(..) -> "" 86 + input.Invalid(error:, ..) -> error 74 87 }), 75 88 ]), 76 89 ])
+4 -11
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/input 4 3 import formz/string_generator/fields 5 4 6 5 pub fn make_form() { ··· 11 10 use c <- formz.with(field("c", fields.number_field())) 12 11 use d <- formz.with(field("d", fields.boolean_field())) 13 12 use e <- formz.with(field("e", fields.email_field())) 14 - use f <- formz.with(field("f", fields.hidden_field())) 15 - use g <- formz.with(field("g", fields.enum_field(letters()))) 16 - use h <- formz.with(field("h", fields.indexed_enum_field(choices))) 17 - use i <- formz.with(field("i", fields.list_field(["Dog", "Cat", "Bird"]))) 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"]))) 18 16 19 - formz.create_form(#(a, b, c, d, e, f, g, h, i)) 20 - } 21 - 22 - pub fn handle_get(form) { 23 - form 24 - |> formz.update_input("f", input.set_value(_, "hidden")) 17 + formz.create_form(#(a, b, c, d, e, f, g, h)) 25 18 } 26 19 27 20 pub type Alphabet {
+64
formz_demo/src/formz_demo/examples/custom_output.gleam
··· 1 + import formz/field 2 + import formz/formz_use as formz 3 + import formz_lustre/fields 4 + import formz_lustre/simple 5 + import gleam/list 6 + import lustre/attribute 7 + import lustre/element/html 8 + 9 + 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 + ) 20 + 21 + formz.create_form(#(billing_address, shipping_address)) 22 + } 23 + 24 + fn address_form() { 25 + use street <- formz.with(field.field("street", fields.text_field())) 26 + use city <- formz.with(field.field("city", fields.text_field())) 27 + use state <- formz.with(field.field("state", fields.list_field(states_list()))) 28 + use postal_code <- formz.with(field.field("postal_code", fields.text_field())) 29 + 30 + formz.create_form(Address(street:, city:, state:, postal_code:)) 31 + } 32 + 33 + pub fn format_form(form) { 34 + let assert Ok(formz.Set(_, billing_inputs)) = formz.get_item(form, "billing") 35 + html.div( 36 + [ 37 + attribute.role("group"), 38 + attribute.attribute("aria-labelledby", "h2"), 39 + attribute.disabled(True), 40 + ], 41 + [ 42 + html.h2([attribute.id("h2")], [html.text("Billing Address")]), 43 + ..list.map(billing_inputs, simple.generate_item) 44 + ], 45 + ) 46 + } 47 + 48 + pub type Address { 49 + Address(street: String, city: String, state: String, postal_code: String) 50 + } 51 + 52 + fn states_list() { 53 + [ 54 + "Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", 55 + "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", 56 + "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", 57 + "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", 58 + "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", 59 + "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", 60 + "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", 61 + "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", 62 + "Washington", "West Virginia", "Wisconsin", "Wyoming", 63 + ] 64 + }
+11 -4
formz_demo/src/formz_demo/examples/labels.gleam
··· 1 1 import formz/field.{field} 2 2 import formz/formz_use as formz 3 + import formz/input.{input} 3 4 import formz/string_generator/fields 4 5 5 6 pub fn make_form() { 6 - use name <- formz.with(field.full("name", "Name", "", fields.text_field())) 7 - use age <- formz.with(field.full("age", "Age", "", fields.integer_field())) 8 - 7 + use name <- formz.with( 8 + field("name", fields.text_field()) 9 + |> field.set_label("Name"), 10 + ) 11 + use age <- formz.with( 12 + field("age", fields.integer_field()) 13 + |> field.set_label("Age"), 14 + ) 9 15 use height <- formz.with( 10 16 field("height", fields.integer_field()) 11 - |> field.set_label("Height (cm)"), 17 + |> field.set_label("Height (cm)") 18 + |> field.set_help_text("Please enter your height in centimeters"), 12 19 ) 13 20 14 21 formz.create_form(#(name, age, height))
+1 -1
formz_demo/src/formz_demo/examples/login.gleam
··· 24 24 } 25 25 26 26 pub fn handle_post(formdata: wisp.FormData, form) { 27 - use cred, form <- formz.parse_and_try(form |> formz.data(formdata.values)) 27 + use cred, form <- formz.try(form |> formz.data(formdata.values)) 28 28 29 29 case cred { 30 30 Credentials("admin", "l33t") -> Ok(User(cred.username))
+15 -1
formz_demo/src/formz_demo/router.gleam
··· 9 9 import wisp.{type Request, type Response} 10 10 11 11 import formz_demo/examples/all_the_inputs 12 + import formz_demo/examples/custom_output 12 13 import formz_demo/examples/hello_world 13 14 import formz_demo/examples/labels 14 15 import formz_demo/examples/login ··· 37 38 run.ExampleRun( 38 39 dir, 39 40 all_the_inputs.make_form, 40 - all_the_inputs.handle_get, 41 + defaults.handle_get, 41 42 defaults.handle_post, 42 43 defaults.format_string_form, 43 44 defaults.formatted_string_form_to_string, ··· 80 81 defaults.handle_post, 81 82 defaults.format_string_form, 82 83 defaults.formatted_string_form_to_string, 84 + ), 85 + ) 86 + }), 87 + #("custom_output", fn(req, dir) { 88 + run.handle( 89 + req, 90 + run.ExampleRun( 91 + dir, 92 + custom_output.make_form, 93 + defaults.handle_get, 94 + defaults.handle_post, 95 + custom_output.format_form, 96 + defaults.formatted_lustre_form_to_string, 83 97 ), 84 98 ) 85 99 }),
+1
formz_lustre/gleam.toml
··· 19 19 20 20 [dev-dependencies] 21 21 gleeunit = ">= 1.0.0 and < 2.0.0" 22 + formz_string = { path = "../formz_string" }
+2
formz_lustre/manifest.toml
··· 3 3 4 4 packages = [ 5 5 { name = "formz", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "justin"], source = "local", path = "../formz" }, 6 + { name = "formz_string", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "../formz_string" }, 6 7 { name = "gleam_erlang", version = "0.27.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "DE468F676D71B313C6C8C5334425CFCF827837333F8AB47B64D8A6D7AA40185D" }, 7 8 { name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" }, 8 9 { name = "gleam_otp", version = "0.12.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BFACC1513410DF5A1617169A9CD7EA334973AC71D860A17574BA7B2EADD89A6F" }, ··· 15 16 16 17 [requirements] 17 18 formz = { path = "../formz" } 19 + formz_string = { path = "../formz_string" } 18 20 gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } 19 21 gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 20 22 lustre = { version = ">= 4.5.1 and < 5.0.0" }
+20 -11
formz_lustre/src/formz_lustre/fields.gleam
··· 1 1 import formz/field 2 + import formz/validation 2 3 import formz_lustre/widgets 3 4 import gleam/list 4 5 5 6 pub fn text_field() { 6 - field.text_field(widgets.text_like_widget("text")) 7 + field.Definition(widgets.text_like_widget("text"), validation.string, "") 7 8 } 8 9 9 10 pub fn email_field() { 10 - field.email_field(widgets.text_like_widget("email")) 11 + field.Definition(widgets.text_like_widget("email"), validation.email, "") 11 12 } 12 13 13 14 pub fn integer_field() { 14 - field.integer_field(widgets.text_like_widget("number")) 15 + field.Definition(widgets.text_like_widget("number"), validation.int, 0) 15 16 } 16 17 17 18 pub fn number_field() { 18 - field.number_field(widgets.text_like_widget("number")) 19 + field.Definition(widgets.text_like_widget("number"), validation.number, 0.0) 19 20 } 20 21 21 22 pub fn boolean_field() { 22 - field.boolean_field(widgets.checkbox_widget()) 23 + field.Definition(widgets.checkbox_widget(), validation.boolean, False) 23 24 } 24 25 25 26 pub fn enum_field(variants: List(#(String, enum))) { 26 - field.enum_field(variants, widgets.select_widget(variants)) 27 + let assert Ok(#(_, first)) = list.first(variants) 28 + field.Definition( 29 + widgets.select_widget(variants), 30 + validation.enum(variants) 31 + |> validation.replace_error("Please select an option"), 32 + first, 33 + ) 27 34 } 28 35 29 36 pub fn indexed_enum_field(variants: List(#(String, enum))) { 30 37 let keys_indexed = list.index_map(variants, fn(t, i) { #(t.0, i) }) 31 - field.indexed_enum_field(variants, widgets.select_widget(keys_indexed)) 38 + let assert Ok(#(_, first)) = list.first(variants) 39 + field.Definition( 40 + widgets.select_widget(keys_indexed), 41 + validation.enum_by_index(variants) 42 + |> validation.replace_error("Please select an option"), 43 + first, 44 + ) 32 45 } 33 46 34 47 pub fn list_field(variants: List(String)) { 35 48 let tuple_list = list.map(variants, fn(s) { #(s, s) }) 36 49 indexed_enum_field(tuple_list) 37 50 } 38 - 39 - pub fn hidden_field() { 40 - field.text_field(widgets.text_widget()) |> field.set_hidden(True) 41 - }
+39 -44
formz_lustre/src/formz_lustre/simple.gleam
··· 1 1 import formz/formz_use as formz 2 - import formz/input.{type Input, Input, InvalidInput, WidgetArgs} 2 + import formz/input.{WidgetArgs} 3 3 import gleam/list 4 4 import gleam/string 5 5 import lustre/attribute ··· 8 8 9 9 pub fn generate_form(form) -> element.Element(msg) { 10 10 form 11 - |> formz.get_inputs 12 - |> list.filter(fn(f) { !f.hidden }) 13 - |> list.map(generate_visible_field) 11 + |> formz.get_items 12 + |> list.map(generate_item) 14 13 |> element.fragment 15 - // <> { 16 - // form 17 - // |> formz.get_inputs 18 - // |> list.filter(fn(f) { f.hidden }) 19 - // |> list.map(generate_hidden_field) 20 - // |> string.join("\n") 21 - // } 22 14 } 23 15 24 - pub fn generate_visible_field( 25 - f: Input(element.Element(msg)), 16 + pub fn generate_item( 17 + item: formz.FormItem(element.Element(msg)), 26 18 ) -> element.Element(msg) { 27 - let label_el = html.label([], [html.text(f.label), html.text(": ")]) 19 + case item { 20 + formz.Item(f) if f.hidden == True -> 21 + html.input([ 22 + attribute.type_("hidden"), 23 + attribute.name(f.name), 24 + attribute.value(f.value), 25 + ]) 26 + formz.Item(f) -> { 27 + let label_el = html.label([], [html.text(f.label), html.text(": ")]) 28 28 29 - let description_el = case string.is_empty(f.help_text) { 30 - True -> element.none() 31 - False -> html.span([attribute.class("help_text")], [html.text(f.help_text)]) 32 - } 33 - let widget_el = 34 - html.span([attribute.class("widget")], [ 35 - f.widget(f, WidgetArgs(id: f.name, labelled_by: input.Element)), 36 - ]) 29 + let description_el = case string.is_empty(f.help_text) { 30 + True -> element.none() 31 + False -> 32 + html.span([attribute.class("help_text")], [html.text(f.help_text)]) 33 + } 34 + let widget_el = 35 + html.span([attribute.class("widget")], [ 36 + f.widget(f, WidgetArgs(id: f.name, labelled_by: input.Element)), 37 + ]) 37 38 38 - let errors_el = case f { 39 - Input(..) -> element.none() 40 - InvalidInput(error:, ..) -> 41 - html.span([attribute.class("errors")], [html.text(error)]) 42 - } 39 + let errors_el = case f { 40 + input.Valid(..) -> element.none() 41 + input.Invalid(error:, ..) -> 42 + html.span([attribute.class("errors")], [html.text(error)]) 43 + } 43 44 44 - html.p([attribute.class("simple_field")], [ 45 - label_el, 46 - description_el, 47 - widget_el, 48 - errors_el, 49 - ]) 50 - } 51 - 52 - pub fn generate_hidden_field(f: Input(String)) -> String { 53 - case f.hidden { 54 - False -> "" 55 - True -> { 56 - "<input type=\"hidden\" name=\"" 57 - <> f.name 58 - <> "\" value=\"" 59 - <> f.value 60 - <> "\" />" 45 + html.p([attribute.class("simple_field")], [ 46 + label_el, 47 + description_el, 48 + widget_el, 49 + errors_el, 50 + ]) 51 + } 52 + formz.Set(s, items) -> { 53 + let legend = html.legend([], [html.text(s.label)]) 54 + let children = items |> list.map(generate_item) 55 + html.fieldset([], [legend, ..children]) 61 56 } 62 57 } 63 58 }
+10 -7
formz_lustre/src/formz_lustre/widgets.gleam
··· 135 135 id_attr(args.id), 136 136 aria_label_attr(args.labelled_by, input.label), 137 137 ], 138 - list.map(variants, fn(variant) { 139 - let val = string.inspect(variant.1) 140 - html.option( 141 - [value_attr(val), attribute.selected(input.value == val)], 142 - variant.0, 143 - ) 144 - }), 138 + list.flatten([ 139 + [html.option([attribute.value("")], "Select..."), html.hr([])], 140 + list.map(variants, fn(variant) { 141 + let val = string.inspect(variant.1) 142 + html.option( 143 + [value_attr(val), attribute.selected(input.value == val)], 144 + variant.0, 145 + ) 146 + }), 147 + ]), 145 148 ) 146 149 } 147 150 }
+19 -34
formz_lustre/test/formz_lustre/fields_test.gleam
··· 2 2 import gleeunit 3 3 import gleeunit/should 4 4 5 - import formz/string_generator/fields as string_fields 6 - import formz_lustre/fields as lustre_fields 5 + import formz_lustre/fields 6 + import formz_string/fields as string_fields 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.Field(format1, output), 14 - field2: field.Field(format2, output), 13 + field1: field.Definition(format1, output), 14 + field2: field.Definition(format2, output), 15 15 ) { 16 - field1.default |> should.equal(field2.default) 17 - 18 - field1.input.name |> should.equal(field2.input.name) 19 - field1.input.label |> should.equal(field2.input.label) 20 - field1.input.help_text |> should.equal(field2.input.help_text) 21 - field1.input.value |> should.equal(field2.input.value) 22 - field1.input.hidden |> should.equal(field2.input.hidden) 23 - 16 + field1.placeholder |> should.equal(field2.placeholder) 24 17 field1.transform |> should.equal(field2.transform) 25 18 } 26 19 27 20 pub fn text_field_test() { 28 21 let string_field = string_fields.text_field() 29 - let lustre_field = lustre_fields.text_field() 22 + let field = fields.text_field() 30 23 31 - fields_should_be_equal_except_widget(lustre_field, string_field) 24 + fields_should_be_equal_except_widget(field, string_field) 32 25 } 33 26 34 27 pub fn email_field_test() { 35 28 let string_field = string_fields.email_field() 36 - let lustre_field = lustre_fields.email_field() 29 + let field = fields.email_field() 37 30 38 - fields_should_be_equal_except_widget(lustre_field, string_field) 31 + fields_should_be_equal_except_widget(field, string_field) 39 32 } 40 33 41 34 pub fn number_field_test() { 42 35 let string_field = string_fields.number_field() 43 - let lustre_field = lustre_fields.number_field() 36 + let field = fields.number_field() 44 37 45 - fields_should_be_equal_except_widget(lustre_field, string_field) 38 + fields_should_be_equal_except_widget(field, string_field) 46 39 } 47 40 48 41 pub fn integer_field_test() { 49 42 let string_field = string_fields.integer_field() 50 - let lustre_field = lustre_fields.integer_field() 43 + let field = fields.integer_field() 51 44 52 - fields_should_be_equal_except_widget(lustre_field, string_field) 45 + fields_should_be_equal_except_widget(field, string_field) 53 46 } 54 47 55 48 pub fn boolean_field_test() { 56 49 let string_field = string_fields.boolean_field() 57 - let lustre_field = lustre_fields.boolean_field() 58 - 59 - fields_should_be_equal_except_widget(lustre_field, string_field) 60 - } 61 - 62 - pub fn hidden_field_test() { 63 - let string_field = string_fields.hidden_field() 64 - let lustre_field = lustre_fields.hidden_field() 50 + let field = fields.boolean_field() 65 51 66 - fields_should_be_equal_except_widget(lustre_field, string_field) 52 + fields_should_be_equal_except_widget(field, string_field) 67 53 } 68 54 69 55 pub fn enum_field_test() { 70 56 let string_field = string_fields.enum_field([#("a", "A"), #("b", "B")]) 71 - let lustre_field = lustre_fields.enum_field([#("a", "A"), #("b", "B")]) 57 + let field = fields.enum_field([#("a", "A"), #("b", "B")]) 72 58 73 - fields_should_be_equal_except_widget(lustre_field, string_field) 59 + fields_should_be_equal_except_widget(field, string_field) 74 60 } 75 61 76 62 pub fn indexed_enum_field_test() { 77 63 let string_field = 78 64 string_fields.indexed_enum_field([#("a", "A"), #("b", "B")]) 79 - let lustre_field = 80 - lustre_fields.indexed_enum_field([#("a", "A"), #("b", "B")]) 65 + let field = fields.indexed_enum_field([#("a", "A"), #("b", "B")]) 81 66 82 - fields_should_be_equal_except_widget(lustre_field, string_field) 67 + fields_should_be_equal_except_widget(field, string_field) 83 68 }
+68 -9
formz_lustre/test/formz_lustre/widgets_test.gleam
··· 3 3 import gleeunit/should 4 4 import lustre/element 5 5 6 - import formz/string_generator/widgets as string_widgets 7 6 import formz_lustre/widgets 7 + import formz_string/widgets as string_widgets 8 8 9 9 pub fn main() { 10 10 gleeunit.main() ··· 18 18 fn test_inputs( 19 19 name name, 20 20 label label, 21 - help help, 21 + help help_text, 22 22 hidden hidden, 23 + disabled disabled, 24 + required required, 23 25 value value, 24 26 args args, 25 27 string string_widget, 26 28 widget widget, 27 29 ) { 28 30 let string_input = 29 - input.Input(name, label, help, string_widget, hidden, value) 30 - let input = input.Input(name, label, help, widget, hidden, value) 31 + input.Valid( 32 + name:, 33 + label:, 34 + help_text:, 35 + widget: string_widget, 36 + hidden:, 37 + value:, 38 + disabled:, 39 + required:, 40 + ) 41 + let input = 42 + input.Valid( 43 + name:, 44 + label:, 45 + help_text:, 46 + widget: widget, 47 + hidden:, 48 + value:, 49 + disabled:, 50 + required:, 51 + ) 31 52 32 53 input.widget(input, args) 33 54 |> convert_to_string ··· 42 63 label: "A", 43 64 help: "help", 44 65 hidden: False, 66 + disabled: False, 67 + required: True, 45 68 value: "", 46 69 args: WidgetArgs("id", labelled_by: input.Element), 47 70 ) ··· 53 76 label: "A", 54 77 help: "help", 55 78 hidden: False, 79 + disabled: False, 80 + required: True, 56 81 value: "", 57 82 args: WidgetArgs("id", labelled_by: input.Element), 58 83 ) ··· 64 89 label: "A", 65 90 help: "help", 66 91 hidden: False, 92 + disabled: False, 93 + required: True, 67 94 value: "val", 68 95 args: WidgetArgs("id", labelled_by: input.Element), 69 96 ) ··· 75 102 label: "A", 76 103 help: "help", 77 104 hidden: False, 105 + disabled: False, 106 + required: True, 78 107 value: "", 79 108 args: WidgetArgs("id", labelled_by: input.Value), 80 109 ) ··· 88 117 label: "A", 89 118 help: "help", 90 119 hidden: False, 120 + disabled: False, 121 + required: True, 91 122 value: "", 92 - args: WidgetArgs("id", labelled_by: input.Value), 123 + args: WidgetArgs("id", labelled_by: input.Id("div")), 93 124 ) 94 125 test_inputs( 95 126 string_widgets.checkbox_widget(), ··· 98 129 label: "A", 99 130 help: "help", 100 131 hidden: False, 132 + disabled: False, 133 + required: True, 101 134 value: "on", 102 135 args: WidgetArgs("id", labelled_by: input.Value), 103 136 ) ··· 109 142 label: "A", 110 143 help: "help", 111 144 hidden: False, 145 + disabled: False, 146 + required: True, 112 147 value: "on", 113 148 args: WidgetArgs("id", labelled_by: input.Element), 114 149 ) ··· 122 157 label: "A", 123 158 help: "help", 124 159 hidden: False, 160 + disabled: False, 161 + required: True, 125 162 value: "", 126 - args: WidgetArgs("id", labelled_by: input.Value), 163 + args: WidgetArgs("id", labelled_by: input.Id("div")), 127 164 ) 128 165 test_inputs( 129 166 string_widgets.password_widget(), ··· 132 169 label: "A", 133 170 help: "help", 134 171 hidden: False, 172 + disabled: False, 173 + required: True, 135 174 value: "xxxx", 136 175 args: WidgetArgs("id", labelled_by: input.Value), 137 176 ) ··· 143 182 label: "A", 144 183 help: "help", 145 184 hidden: False, 185 + disabled: False, 186 + required: True, 146 187 value: "xxxx", 147 188 args: WidgetArgs("id", labelled_by: input.Element), 148 189 ) ··· 156 197 label: "A", 157 198 help: "help", 158 199 hidden: False, 200 + disabled: False, 201 + required: True, 159 202 value: "", 160 - args: WidgetArgs("id", labelled_by: input.Value), 203 + args: WidgetArgs("id", labelled_by: input.Id("div")), 161 204 ) 162 205 test_inputs( 163 206 string_widgets.textarea_widget(), ··· 166 209 label: "A", 167 210 help: "help", 168 211 hidden: False, 212 + disabled: False, 213 + required: True, 169 214 value: "1", 170 215 args: WidgetArgs("id", labelled_by: input.Value), 171 216 ) ··· 177 222 label: "A", 178 223 help: "help", 179 224 hidden: False, 225 + disabled: False, 226 + required: True, 180 227 value: "1", 181 228 args: WidgetArgs("id", labelled_by: input.Element), 182 229 ) ··· 190 237 label: "A", 191 238 help: "help", 192 239 hidden: False, 240 + disabled: False, 241 + required: True, 193 242 value: "", 194 - args: WidgetArgs("id", labelled_by: input.Value), 243 + args: WidgetArgs("id", labelled_by: input.Id("div")), 195 244 ) 196 245 test_inputs( 197 246 string_widgets.hidden_widget(), ··· 200 249 label: "A", 201 250 help: "help", 202 251 hidden: False, 252 + disabled: False, 253 + required: True, 203 254 value: "1", 204 255 args: WidgetArgs("id", labelled_by: input.Value), 205 256 ) ··· 211 262 label: "A", 212 263 help: "help", 213 264 hidden: False, 265 + disabled: False, 266 + required: True, 214 267 value: "1", 215 268 args: WidgetArgs("id", labelled_by: input.Element), 216 269 ) ··· 225 278 label: "A", 226 279 help: "help", 227 280 hidden: False, 281 + disabled: False, 282 + required: True, 228 283 value: "", 229 - args: WidgetArgs("id", labelled_by: input.Value), 284 + args: WidgetArgs("id", labelled_by: input.Id("div")), 230 285 ) 231 286 test_inputs( 232 287 string_widgets.select_widget(list), ··· 235 290 label: "A", 236 291 help: "help", 237 292 hidden: False, 293 + disabled: False, 294 + required: True, 238 295 value: "1", 239 296 args: WidgetArgs("id", labelled_by: input.Value), 240 297 ) ··· 246 303 label: "A", 247 304 help: "help", 248 305 hidden: False, 306 + disabled: False, 307 + required: True, 249 308 value: "1", 250 309 args: WidgetArgs("id", labelled_by: input.Element), 251 310 )
+1
formz_nakai/gleam.toml
··· 19 19 20 20 [dev-dependencies] 21 21 gleeunit = ">= 1.0.0 and < 2.0.0" 22 + formz_string = { path = "../formz_string" }
+2
formz_nakai/manifest.toml
··· 3 3 4 4 packages = [ 5 5 { name = "formz", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "justin"], source = "local", path = "../formz" }, 6 + { name = "formz_string", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "../formz_string" }, 6 7 { name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" }, 7 8 { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, 8 9 { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, ··· 11 12 12 13 [requirements] 13 14 formz = { path = "../formz" } 15 + formz_string = { path = "../formz_string" } 14 16 gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } 15 17 gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 16 18 nakai = { version = ">= 1.0.0 and < 2.0.0" }
+20 -11
formz_nakai/src/formz_nakai/fields.gleam
··· 1 1 import formz/field 2 + import formz/validation 2 3 import formz_nakai/widgets 3 4 import gleam/list 4 5 5 6 pub fn text_field() { 6 - field.text_field(widgets.text_like_widget("text")) 7 + field.Definition(widgets.text_like_widget("text"), validation.string, "") 7 8 } 8 9 9 10 pub fn email_field() { 10 - field.email_field(widgets.text_like_widget("email")) 11 + field.Definition(widgets.text_like_widget("email"), validation.email, "") 11 12 } 12 13 13 14 pub fn integer_field() { 14 - field.integer_field(widgets.text_like_widget("number")) 15 + field.Definition(widgets.text_like_widget("number"), validation.int, 0) 15 16 } 16 17 17 18 pub fn number_field() { 18 - field.number_field(widgets.text_like_widget("number")) 19 + field.Definition(widgets.text_like_widget("number"), validation.number, 0.0) 19 20 } 20 21 21 22 pub fn boolean_field() { 22 - field.boolean_field(widgets.checkbox_widget()) 23 + field.Definition(widgets.checkbox_widget(), validation.boolean, False) 23 24 } 24 25 25 26 pub fn enum_field(variants: List(#(String, enum))) { 26 - field.enum_field(variants, widgets.select_widget(variants)) 27 + let assert Ok(#(_, first)) = list.first(variants) 28 + field.Definition( 29 + widgets.select_widget(variants), 30 + validation.enum(variants) 31 + |> validation.replace_error("Please select an option"), 32 + first, 33 + ) 27 34 } 28 35 29 36 pub fn indexed_enum_field(variants: List(#(String, enum))) { 30 37 let keys_indexed = list.index_map(variants, fn(t, i) { #(t.0, i) }) 31 - field.indexed_enum_field(variants, widgets.select_widget(keys_indexed)) 38 + let assert Ok(#(_, first)) = list.first(variants) 39 + field.Definition( 40 + widgets.select_widget(keys_indexed), 41 + validation.enum_by_index(variants) 42 + |> validation.replace_error("Please select an option"), 43 + first, 44 + ) 32 45 } 33 46 34 47 pub fn list_field(variants: List(String)) { 35 48 let tuple_list = list.map(variants, fn(s) { #(s, s) }) 36 49 indexed_enum_field(tuple_list) 37 50 } 38 - 39 - pub fn hidden_field() { 40 - field.text_field(widgets.hidden_widget()) |> field.set_hidden(True) 41 - }
+35 -44
formz_nakai/src/formz_nakai/simple.gleam
··· 1 1 import formz/formz_use as formz 2 - import formz/input.{type Input, Input, InvalidInput, WidgetArgs} 2 + import formz/input.{WidgetArgs} 3 3 import gleam/list 4 4 import gleam/string 5 5 import nakai/attr ··· 7 7 8 8 pub fn generate_form(form) -> html.Node { 9 9 form 10 - |> formz.get_inputs 11 - |> list.filter(fn(f) { !f.hidden }) 12 - |> list.map(generate_visible_field) 13 - |> html.Fragment 14 - // <> { 15 - // form 16 - // |> formz.get_inputs 17 - // |> list.filter(fn(f) { f.hidden }) 18 - // |> list.map(generate_hidden_field) 19 - // |> string.join("\n") 20 - // } 10 + |> formz.get_items 11 + |> list.map(generate_visible_item) 12 + |> html.Fragment() 21 13 } 22 14 23 - pub fn generate_visible_field(f: Input(html.Node)) -> html.Node { 24 - let label_el = html.label([], [html.Text(f.label), html.Text(": ")]) 15 + pub fn generate_visible_item(item: formz.FormItem(html.Node)) -> html.Node { 16 + case item { 17 + formz.Item(f) if f.hidden == True -> 18 + html.input([attr.type_("hidden"), attr.name(f.name), attr.value(f.value)]) 19 + formz.Item(f) -> { 20 + let label_el = 21 + html.label([attr.for(f.name)], [html.Text(f.label), html.Text(": ")]) 25 22 26 - let description_el = case string.is_empty(f.help_text) { 27 - True -> html.Nothing 28 - False -> html.span([attr.class("help_text")], [html.Text(f.help_text)]) 29 - } 30 - let widget_el = 31 - html.span([attr.class("widget")], [ 32 - f.widget(f, WidgetArgs(id: f.name, labelled_by: input.Element)), 33 - ]) 23 + let description_el = case string.is_empty(f.help_text) { 24 + True -> html.Nothing 25 + False -> html.span([attr.class("help_text")], [html.Text(f.help_text)]) 26 + } 27 + let widget_el = 28 + html.span([attr.class("widget")], [ 29 + f.widget(f, WidgetArgs(id: f.name, labelled_by: input.Element)), 30 + ]) 34 31 35 - let errors_el = case f { 36 - Input(..) -> html.Nothing 37 - InvalidInput(error:, ..) -> 38 - html.span([attr.class("errors")], [html.Text(error)]) 39 - } 32 + let errors_el = case f { 33 + input.Valid(..) -> html.Nothing 34 + input.Invalid(error:, ..) -> 35 + html.span([attr.class("errors")], [html.Text(error)]) 36 + } 40 37 41 - html.p([attr.class("simple_field")], [ 42 - label_el, 43 - description_el, 44 - widget_el, 45 - errors_el, 46 - ]) 47 - } 48 - 49 - pub fn generate_hidden_field(f: Input(String)) -> String { 50 - case f.hidden { 51 - False -> "" 52 - True -> { 53 - "<input type=\"hidden\" name=\"" 54 - <> f.name 55 - <> "\" value=\"" 56 - <> f.value 57 - <> "\" />" 38 + html.p([attr.class("simple_field")], [ 39 + label_el, 40 + description_el, 41 + widget_el, 42 + errors_el, 43 + ]) 44 + } 45 + formz.Set(s, items) -> { 46 + let legend = html.legend([], [html.Text(s.label)]) 47 + let children = items |> list.map(generate_visible_item) 48 + html.fieldset([], [legend, ..children]) 58 49 } 59 50 } 60 51 }
+13 -10
formz_nakai/src/formz_nakai/widgets.gleam
··· 124 124 id_attr(args.id), 125 125 aria_label_attr(args.labelled_by, input.label), 126 126 ]), 127 - list.map(variants, fn(variant) { 128 - let val = string.inspect(variant.1) 129 - let selected_attr = case input.value == val { 130 - True -> [attr.selected()] 131 - _ -> [] 132 - } 133 - html.option(list.flatten([value_attr(val), selected_attr]), [ 134 - html.Text(variant.0), 135 - ]) 136 - }), 127 + list.flatten([ 128 + [html.option([attr.value("")], [html.Text("Select...")]), html.hr([])], 129 + list.map(variants, fn(variant) { 130 + let val = string.inspect(variant.1) 131 + let selected_attr = case input.value == val { 132 + True -> [attr.selected()] 133 + _ -> [] 134 + } 135 + html.option(list.flatten([value_attr(val), selected_attr]), [ 136 + html.Text(variant.0), 137 + ]) 138 + }), 139 + ]), 137 140 ) 138 141 } 139 142 }
+19 -33
formz_nakai/test/formz_nakai/fields_test.gleam
··· 2 2 import gleeunit 3 3 import gleeunit/should 4 4 5 - import formz/string_generator/fields as string_fields 6 - import formz_nakai/fields as nakai_fields 5 + import formz_nakai/fields 6 + import formz_string/fields as string_fields 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.Field(format1, output), 14 - field2: field.Field(format2, output), 13 + field1: field.Definition(format1, output), 14 + field2: field.Definition(format2, output), 15 15 ) { 16 - field1.default |> should.equal(field2.default) 17 - 18 - field1.input.name |> should.equal(field2.input.name) 19 - field1.input.label |> should.equal(field2.input.label) 20 - field1.input.help_text |> should.equal(field2.input.help_text) 21 - field1.input.value |> should.equal(field2.input.value) 22 - field1.input.hidden |> should.equal(field2.input.hidden) 23 - 16 + field1.placeholder |> should.equal(field2.placeholder) 24 17 field1.transform |> should.equal(field2.transform) 25 18 } 26 19 27 20 pub fn text_field_test() { 28 21 let string_field = string_fields.text_field() 29 - let nakai_field = nakai_fields.text_field() 22 + let field = fields.text_field() 30 23 31 - fields_should_be_equal_except_widget(nakai_field, string_field) 24 + fields_should_be_equal_except_widget(field, string_field) 32 25 } 33 26 34 27 pub fn email_field_test() { 35 28 let string_field = string_fields.email_field() 36 - let nakai_field = nakai_fields.email_field() 29 + let field = fields.email_field() 37 30 38 - fields_should_be_equal_except_widget(nakai_field, string_field) 31 + fields_should_be_equal_except_widget(field, string_field) 39 32 } 40 33 41 34 pub fn number_field_test() { 42 35 let string_field = string_fields.number_field() 43 - let nakai_field = nakai_fields.number_field() 36 + let field = fields.number_field() 44 37 45 - fields_should_be_equal_except_widget(nakai_field, string_field) 38 + fields_should_be_equal_except_widget(field, string_field) 46 39 } 47 40 48 41 pub fn integer_field_test() { 49 42 let string_field = string_fields.integer_field() 50 - let nakai_field = nakai_fields.integer_field() 43 + let field = fields.integer_field() 51 44 52 - fields_should_be_equal_except_widget(nakai_field, string_field) 45 + fields_should_be_equal_except_widget(field, string_field) 53 46 } 54 47 55 48 pub fn boolean_field_test() { 56 49 let string_field = string_fields.boolean_field() 57 - let nakai_field = nakai_fields.boolean_field() 58 - 59 - fields_should_be_equal_except_widget(nakai_field, string_field) 60 - } 61 - 62 - pub fn hidden_field_test() { 63 - let string_field = string_fields.hidden_field() 64 - let nakai_field = nakai_fields.hidden_field() 50 + let field = fields.boolean_field() 65 51 66 - fields_should_be_equal_except_widget(nakai_field, string_field) 52 + fields_should_be_equal_except_widget(field, string_field) 67 53 } 68 54 69 55 pub fn enum_field_test() { 70 56 let string_field = string_fields.enum_field([#("a", "A"), #("b", "B")]) 71 - let nakai_field = nakai_fields.enum_field([#("a", "A"), #("b", "B")]) 57 + let field = fields.enum_field([#("a", "A"), #("b", "B")]) 72 58 73 - fields_should_be_equal_except_widget(nakai_field, string_field) 59 + fields_should_be_equal_except_widget(field, string_field) 74 60 } 75 61 76 62 pub fn indexed_enum_field_test() { 77 63 let string_field = 78 64 string_fields.indexed_enum_field([#("a", "A"), #("b", "B")]) 79 - let nakai_field = nakai_fields.indexed_enum_field([#("a", "A"), #("b", "B")]) 65 + let field = fields.indexed_enum_field([#("a", "A"), #("b", "B")]) 80 66 81 - fields_should_be_equal_except_widget(nakai_field, string_field) 67 + fields_should_be_equal_except_widget(field, string_field) 82 68 }
+70 -6
formz_nakai/test/formz_nakai/widgets_test.gleam
··· 1 - import formz/input.{type Input, type WidgetArgs, Input, WidgetArgs} 1 + import formz/input.{type WidgetArgs, WidgetArgs} 2 2 import gleam/string 3 3 import gleeunit 4 4 import gleeunit/should 5 5 import nakai 6 6 7 - import formz/string_generator/widgets as string_widgets 8 7 import formz_nakai/widgets 8 + import formz_string/widgets as string_widgets 9 9 10 10 pub fn main() { 11 11 gleeunit.main() ··· 19 19 string.replace(str, "checked=\"true\"", "checked") 20 20 } 21 21 22 + fn remove_empty_attributes(str: String) -> String { 23 + string.replace(str, "=\"\"", "") 24 + } 25 + 22 26 fn convert_to_string(input) { 23 27 input 24 28 |> nakai.to_inline_string 25 29 |> remove_self_closing_slash 26 30 |> remove_checked_true 31 + |> remove_empty_attributes 27 32 } 28 33 29 34 fn test_inputs( 30 35 name name, 31 36 label label, 32 - help help, 37 + help help_text, 33 38 hidden hidden, 39 + disabled disabled, 40 + required required, 34 41 value value, 35 42 args args, 36 43 string string_widget, 37 44 widget widget, 38 45 ) { 39 - let string_input = Input(name, label, help, string_widget, hidden, value) 40 - let input = Input(name, label, help, widget, hidden, value) 46 + let string_input = 47 + input.Valid( 48 + name:, 49 + label:, 50 + help_text:, 51 + widget: string_widget, 52 + hidden:, 53 + value:, 54 + disabled:, 55 + required:, 56 + ) 57 + let input = 58 + input.Valid( 59 + name:, 60 + label:, 61 + help_text:, 62 + widget: widget, 63 + hidden:, 64 + value:, 65 + disabled:, 66 + required:, 67 + ) 41 68 42 69 input.widget(input, args) 43 70 |> convert_to_string ··· 52 79 label: "A", 53 80 help: "help", 54 81 hidden: False, 82 + disabled: False, 83 + required: True, 55 84 value: "", 56 85 args: WidgetArgs("id", labelled_by: input.Element), 57 86 ) 58 - 59 87 test_inputs( 60 88 string_widgets.text_like_widget("text"), 61 89 widgets.text_like_widget("text"), ··· 63 91 label: "A", 64 92 help: "help", 65 93 hidden: False, 94 + disabled: False, 95 + required: True, 66 96 value: "", 67 97 args: WidgetArgs("id", labelled_by: input.Element), 68 98 ) ··· 74 104 label: "A", 75 105 help: "help", 76 106 hidden: False, 107 + disabled: False, 108 + required: True, 77 109 value: "val", 78 110 args: WidgetArgs("id", labelled_by: input.Element), 79 111 ) ··· 85 117 label: "A", 86 118 help: "help", 87 119 hidden: False, 120 + disabled: False, 121 + required: True, 88 122 value: "", 89 123 args: WidgetArgs("id", labelled_by: input.Value), 90 124 ) ··· 98 132 label: "A", 99 133 help: "help", 100 134 hidden: False, 135 + disabled: False, 136 + required: True, 101 137 value: "", 102 138 args: WidgetArgs("id", labelled_by: input.Id("div")), 103 139 ) ··· 108 144 label: "A", 109 145 help: "help", 110 146 hidden: False, 147 + disabled: False, 148 + required: True, 111 149 value: "on", 112 150 args: WidgetArgs("id", labelled_by: input.Value), 113 151 ) ··· 119 157 label: "A", 120 158 help: "help", 121 159 hidden: False, 160 + disabled: False, 161 + required: True, 122 162 value: "on", 123 163 args: WidgetArgs("id", labelled_by: input.Element), 124 164 ) ··· 132 172 label: "A", 133 173 help: "help", 134 174 hidden: False, 175 + disabled: False, 176 + required: True, 135 177 value: "", 136 178 args: WidgetArgs("id", labelled_by: input.Id("div")), 137 179 ) ··· 142 184 label: "A", 143 185 help: "help", 144 186 hidden: False, 187 + disabled: False, 188 + required: True, 145 189 value: "xxxx", 146 190 args: WidgetArgs("id", labelled_by: input.Value), 147 191 ) ··· 153 197 label: "A", 154 198 help: "help", 155 199 hidden: False, 200 + disabled: False, 201 + required: True, 156 202 value: "xxxx", 157 203 args: WidgetArgs("id", labelled_by: input.Element), 158 204 ) ··· 166 212 label: "A", 167 213 help: "help", 168 214 hidden: False, 215 + disabled: False, 216 + required: True, 169 217 value: "", 170 218 args: WidgetArgs("id", labelled_by: input.Id("div")), 171 219 ) ··· 176 224 label: "A", 177 225 help: "help", 178 226 hidden: False, 227 + disabled: False, 228 + required: True, 179 229 value: "1", 180 230 args: WidgetArgs("id", labelled_by: input.Value), 181 231 ) ··· 187 237 label: "A", 188 238 help: "help", 189 239 hidden: False, 240 + disabled: False, 241 + required: True, 190 242 value: "1", 191 243 args: WidgetArgs("id", labelled_by: input.Element), 192 244 ) ··· 200 252 label: "A", 201 253 help: "help", 202 254 hidden: False, 255 + disabled: False, 256 + required: True, 203 257 value: "", 204 258 args: WidgetArgs("id", labelled_by: input.Id("div")), 205 259 ) ··· 210 264 label: "A", 211 265 help: "help", 212 266 hidden: False, 267 + disabled: False, 268 + required: True, 213 269 value: "1", 214 270 args: WidgetArgs("id", labelled_by: input.Value), 215 271 ) ··· 221 277 label: "A", 222 278 help: "help", 223 279 hidden: False, 280 + disabled: False, 281 + required: True, 224 282 value: "1", 225 283 args: WidgetArgs("id", labelled_by: input.Element), 226 284 ) ··· 235 293 label: "A", 236 294 help: "help", 237 295 hidden: False, 296 + disabled: False, 297 + required: True, 238 298 value: "", 239 299 args: WidgetArgs("id", labelled_by: input.Id("div")), 240 300 ) ··· 245 305 label: "A", 246 306 help: "help", 247 307 hidden: False, 308 + disabled: False, 309 + required: True, 248 310 value: "1", 249 311 args: WidgetArgs("id", labelled_by: input.Value), 250 312 ) ··· 256 318 label: "A", 257 319 help: "help", 258 320 hidden: False, 321 + disabled: False, 322 + required: True, 259 323 value: "1", 260 324 args: WidgetArgs("id", labelled_by: input.Element), 261 325 )
+23
formz_string/.github/workflows/test.yml
··· 1 + name: test 2 + 3 + on: 4 + push: 5 + branches: 6 + - master 7 + - main 8 + pull_request: 9 + 10 + jobs: 11 + test: 12 + runs-on: ubuntu-latest 13 + steps: 14 + - uses: actions/checkout@v4 15 + - uses: erlef/setup-beam@v1 16 + with: 17 + otp-version: "26.0.2" 18 + gleam-version: "1.5.0" 19 + rebar3-version: "3" 20 + # elixir-version: "1.15.4" 21 + - run: gleam deps download 22 + - run: gleam test 23 + - run: gleam format --check src test
+4
formz_string/.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump
+24
formz_string/README.md
··· 1 + # formz_string 2 + 3 + [![Package Version](https://img.shields.io/hexpm/v/formz_string)](https://hex.pm/packages/formz_string) 4 + [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/formz_string/) 5 + 6 + ```sh 7 + gleam add formz_string@1 8 + ``` 9 + ```gleam 10 + import formz_string 11 + 12 + pub fn main() { 13 + // TODO: An example of the project in use 14 + } 15 + ``` 16 + 17 + Further documentation can be found at <https://hexdocs.pm/formz_string>. 18 + 19 + ## Development 20 + 21 + ```sh 22 + gleam run # Run the project 23 + gleam test # Run the tests 24 + ```
+20
formz_string/gleam.toml
··· 1 + name = "formz_string" 2 + version = "1.0.0" 3 + 4 + # Fill out these fields if you intend to generate HTML documentation or publish 5 + # your project to the Hex package manager. 6 + # 7 + # description = "" 8 + # licences = ["Apache-2.0"] 9 + # repository = { type = "github", user = "", repo = "" } 10 + # links = [{ title = "Website", href = "" }] 11 + # 12 + # For a full reference of all the available options, you can have a look at 13 + # https://gleam.run/writing-gleam/gleam-toml/. 14 + 15 + [dependencies] 16 + formz = { path = "../formz" } 17 + gleam_stdlib = ">= 0.34.0 and < 2.0.0" 18 + 19 + [dev-dependencies] 20 + gleeunit = ">= 1.0.0 and < 2.0.0"
+14
formz_string/manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "formz", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "justin"], source = "local", path = "../formz" }, 6 + { name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" }, 7 + { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, 8 + { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, 9 + ] 10 + 11 + [requirements] 12 + formz = { path = "../formz" } 13 + gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } 14 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
+5
formz_string/src/formz_string.gleam
··· 1 + import gleam/io 2 + 3 + pub fn main() { 4 + io.println("Hello from formz_string!") 5 + }
+50
formz_string/src/formz_string/fields.gleam
··· 1 + import formz/field 2 + import formz/validation 3 + import formz_string/widgets 4 + import gleam/list 5 + 6 + pub fn text_field() { 7 + field.Definition(widgets.text_like_widget("text"), validation.string, "") 8 + } 9 + 10 + pub fn email_field() { 11 + field.Definition(widgets.text_like_widget("email"), validation.email, "") 12 + } 13 + 14 + pub fn integer_field() { 15 + field.Definition(widgets.text_like_widget("number"), validation.int, 0) 16 + } 17 + 18 + pub fn number_field() { 19 + field.Definition(widgets.text_like_widget("number"), validation.number, 0.0) 20 + } 21 + 22 + pub fn boolean_field() { 23 + field.Definition(widgets.checkbox_widget(), validation.boolean, False) 24 + } 25 + 26 + pub fn enum_field(variants: List(#(String, enum))) { 27 + let assert Ok(#(_, first)) = list.first(variants) 28 + field.Definition( 29 + widgets.select_widget(variants), 30 + validation.enum(variants) 31 + |> validation.replace_error("Please select an option"), 32 + first, 33 + ) 34 + } 35 + 36 + pub fn indexed_enum_field(variants: List(#(String, enum))) { 37 + let keys_indexed = list.index_map(variants, fn(t, i) { #(t.0, i) }) 38 + let assert Ok(#(_, first)) = list.first(variants) 39 + field.Definition( 40 + widgets.select_widget(keys_indexed), 41 + validation.enum_by_index(variants) 42 + |> validation.replace_error("Please select an option"), 43 + first, 44 + ) 45 + } 46 + 47 + pub fn list_field(variants: List(String)) { 48 + let tuple_list = list.map(variants, fn(s) { #(s, s) }) 49 + indexed_enum_field(tuple_list) 50 + }
+12
formz_string/test/formz_string_test.gleam
··· 1 + import gleeunit 2 + import gleeunit/should 3 + 4 + pub fn main() { 5 + gleeunit.main() 6 + } 7 + 8 + // gleeunit test functions end in `_test` 9 + pub fn hello_world_test() { 10 + 1 11 + |> should.equal(1) 12 + }