this repo has no description
4
fork

Configure Feed

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

simplify the built-in definitions and validations

Don’t provide any validations that aren’t specifically used by the basic set of defintions.

Document validation and definition functions.

+408 -380
+168 -107
formz/src/formz/validation.gleam
··· 1 1 import gleam/float 2 2 import gleam/int 3 3 import gleam/list 4 - import gleam/pair 5 4 import gleam/result 6 5 import gleam/string 7 6 8 - pub fn string(str: String) -> Result(String, String) { 9 - case string.trim(str) { 10 - "" -> Error("Must not be empty") 11 - trimmed -> Ok(trimmed) 12 - } 13 - } 14 - 15 - pub fn email(input: String) -> Result(String, String) { 16 - // TODO verify both parts have at least one character? 17 - case input |> string.trim |> string.split("@") { 18 - [_, _] -> Ok(input) 19 - _ -> Error("Must be an email address") 20 - } 21 - } 22 - 23 - pub fn must_be_longer_than(length: Int) -> fn(String) -> Result(String, String) { 24 - fn(input) { 25 - case string.length(input) > length { 26 - True -> Ok(input) 27 - False -> Error("Must be longer than " <> int.to_string(length)) 28 - } 29 - } 30 - } 31 - 32 - pub fn int(str: String) -> Result(Int, String) { 33 - str 34 - |> string.trim 35 - |> int.parse 36 - |> result.replace_error("Must be a whole number") 37 - } 38 - 39 - pub fn number(str: String) -> Result(Float, String) { 40 - let str = string.trim(str) 41 - case float.parse(str) { 42 - Ok(value) -> Ok(value) 43 - Error(_) -> int.parse(str) |> result.map(int.to_float) 44 - } 45 - |> result.replace_error("Must be a number") 46 - } 47 - 48 - pub fn enum( 49 - variants: List(#(String, enum)), 50 - ) -> fn(String) -> Result(enum, String) { 51 - fn(str) { 52 - variants 53 - |> list.filter_map(fn(t) { 54 - case string.inspect(t.1) == str { 55 - True -> Ok(t.1) 56 - False -> Error(Nil) 57 - } 58 - }) 59 - |> list.first 60 - |> result.map_error(map_error_for_list(variants)) 61 - } 62 - } 63 - 64 - pub fn enum_by_index( 65 - variants: List(#(String, enum)), 66 - ) -> fn(String) -> Result(enum, String) { 67 - fn(str) { 68 - let vals_indexed = 69 - list.index_map(variants, fn(t, i) { #(int.to_string(i), t.1) }) 70 - 71 - list.key_find(vals_indexed, str) 72 - |> result.map_error(map_error_for_list(variants)) 73 - } 74 - } 75 - 76 - fn map_error_for_list(variants) { 77 - fn(_) { 78 - case variants { 79 - [] -> "No allowed values" 80 - [a] -> "Must be " <> a |> pair.first 81 - [_, _] | [_, _, _] | [_, _, _, _] -> 82 - "Must be one of " <> list.map(variants, pair.first) |> string.join(", ") 83 - [a, b, c, d, _, ..] -> 84 - "Must be one of " 85 - <> list.map([a, b, c, d], pair.first) |> string.join(", ") 86 - <> "..." 7 + /// Chain validations together. 8 + /// 9 + /// ## Examples 10 + /// 11 + /// ```gleam 12 + /// let is_even = fn(num) { 13 + /// case num % 2 == 0 { 14 + /// True -> Ok(num) 15 + /// False -> Error("must be even") 16 + /// } 17 + /// } 18 + /// let check = and(int, is_even) 19 + /// 20 + /// check("2") 21 + /// # -> Ok(2) 22 + /// 23 + /// check("hi") 24 + /// # -> Error("bust be a whole number") 25 + /// 26 + /// check("1") 27 + /// # -> Error("must be even") 28 + pub fn and( 29 + previous: fn(a) -> Result(b, String), 30 + next: fn(b) -> Result(c, String), 31 + ) -> fn(a) -> Result(c, String) { 32 + fn(data) { 33 + case previous(data) { 34 + Ok(value) -> next(value) 35 + Error(error) -> Error(error) 87 36 } 88 37 } 89 38 } 90 39 40 + /// Parse the input as a boolean in a permissive way. 41 + /// 42 + /// ## Examples 43 + /// 44 + /// ```gleam 45 + /// number("1") 46 + /// number("true") 47 + /// number("yes") 48 + /// number("on") 49 + /// # -> Ok(True) 50 + /// ``` 51 + /// 52 + /// ```gleam 53 + /// number("0") 54 + /// number("") 55 + /// number("no") 56 + /// number("false") 57 + /// number("off") 58 + /// # -> Ok(False) 59 + /// ``` 60 + /// 61 + /// ```gleam 62 + /// number("hi") 63 + /// # -> Error("Must be true or false") 64 + /// ``` 91 65 pub fn boolean(str: String) -> Result(Bool, String) { 92 66 case string.trim(str) { 93 67 "True" -> Ok(True) ··· 105 79 "off" -> Ok(False) 106 80 "0" -> Ok(False) 107 81 "" -> Ok(False) 108 - _ -> Error("Must be true or false") 82 + _ -> Error("must be true or false") 109 83 } 110 84 } 111 85 112 - pub fn true(str: String) -> Result(Bool, String) { 113 - case boolean(str) { 114 - Ok(True) -> Ok(True) 115 - _ -> Error("Must be true") 86 + /// Parse the input as a String that looks like an email address, i.e. it 87 + /// containts an `@` character. 88 + /// 89 + /// ## Examples 90 + /// 91 + /// ```gleam 92 + /// email("hello@example.com") 93 + /// # -> Ok("hello@example.com") 94 + /// ``` 95 + /// 96 + /// ```gleam 97 + /// email("@") 98 + /// # -> Ok("@") 99 + /// ``` 100 + /// 101 + /// ```gleam 102 + /// number("hello") 103 + /// # -> Error("Must be an email address") 104 + /// ``` 105 + /// ```gleam 106 + /// number("1") 107 + /// # -> Error("ust be an email address") 108 + /// ``` 109 + pub fn email(input: String) -> Result(String, String) { 110 + // TODO verify both parts have at least one character? 111 + case input |> string.trim |> string.split("@") { 112 + [_, _] -> Ok(input) 113 + _ -> Error("must be an email address") 116 114 } 117 115 } 118 116 119 - pub fn and( 120 - previous: fn(a) -> Result(b, String), 121 - next: fn(b) -> Result(c, String), 122 - ) -> fn(a) -> Result(c, String) { 123 - fn(data) { 124 - case previous(data) { 125 - Ok(value) -> next(value) 126 - Error(error) -> Error(error) 127 - } 117 + /// Parse the input as a float. this is forgiving and will also parse 118 + /// ints into floats. 119 + /// 120 + /// ## Examples 121 + /// 122 + /// ```gleam 123 + /// number("1") 124 + /// # -> Ok(1.0) 125 + /// ``` 126 + /// 127 + /// ```gleam 128 + /// number("3.4") 129 + /// # -> Ok(3.4) 130 + /// ``` 131 + /// ```gleam 132 + /// number("hello") 133 + /// # -> Error("Must be a number") 134 + /// ``` 135 + pub fn number(str: String) -> Result(Float, String) { 136 + let str = string.trim(str) 137 + case float.parse(str) { 138 + Ok(value) -> Ok(value) 139 + Error(_) -> int.parse(str) |> result.map(int.to_float) 128 140 } 141 + |> result.replace_error("must be a number") 129 142 } 130 143 131 - pub fn or( 132 - previous: fn(a) -> Result(b, String), 133 - next: fn(a) -> Result(b, String), 134 - ) -> fn(a) -> Result(b, String) { 135 - fn(data) { 136 - case previous(data) { 137 - Ok(value) -> Ok(value) 138 - Error(err1) -> 139 - case next(data) { 140 - Ok(value) -> Ok(value) 141 - Error(err2) -> Error(err1 <> " or " <> err2) 142 - } 143 - } 144 + /// Parse the input as an int. 145 + /// 146 + /// ## Examples 147 + /// 148 + /// ```gleam 149 + /// number("1") 150 + /// # -> Ok(1) 151 + /// ``` 152 + /// 153 + /// ```gleam 154 + /// number("3.4") 155 + /// # -> Error("Must be a whole number") 156 + /// ``` 157 + /// ```gleam 158 + /// number("hello") 159 + /// # -> Error("Must be a whole number") 160 + /// ``` 161 + pub fn int(str: String) -> Result(Int, String) { 162 + str 163 + |> string.trim 164 + |> int.parse 165 + |> result.replace_error("must be a whole number") 166 + } 167 + 168 + /// Validates that the input is one from a list of allowed values. Takes a list 169 + /// of Gleam values that can be chosen. This uses the index of the item in to 170 + /// find the desired value. 171 + /// 172 + /// ## Examples 173 + /// 174 + /// ```gleam 175 + /// enum(["One","Two","Three"])("1") 176 + /// # -> Ok("Two") 177 + /// ``` 178 + /// 179 + /// ```gleam 180 + /// enum([True, False])("42") 181 + /// # -> Error("must be an item in list") 182 + /// ``` 183 + /// 184 + /// ```gleam 185 + /// enum([True, False])("ok") 186 + /// # -> Error("must be an item in list") 187 + /// ``` 188 + pub fn list_item_by_index( 189 + variants: List(enum), 190 + ) -> fn(String) -> Result(enum, String) { 191 + fn(str) { 192 + variants 193 + // not the most effecient, but it's simple. and I can't imagine <select>`s 194 + // will get that long in practice. 195 + |> list.index_map(fn(val, i) { #(int.to_string(i), val) }) 196 + |> list.key_find(str) 197 + |> result.replace_error("must be an item in list") 144 198 } 145 199 } 146 200 201 + /// Replace the error message of a validation with a new one. Most of the built-in 202 + /// error messages are pretty rudimentary. 147 203 pub fn replace_error( 148 204 previous: fn(a) -> Result(b, String), 149 205 error: String, 150 206 ) -> fn(a) -> Result(b, String) { 151 207 fn(data) { previous(data) |> result.replace_error(error) } 152 208 } 209 + 210 + /// Default field parser. Trims the input and returns it as is. 211 + pub fn string(str: String) -> Result(String, String) { 212 + Ok(string.trim(str)) 213 + }
+16 -16
formz/test/formz/formz_builder_test.gleam
··· 1 1 import formz/definition 2 2 import formz/field.{field} 3 - import formz/form_details.{form_details} 4 3 import formz/formz_builder.{Element, Set} as formz 4 + import formz/subform.{subform} 5 5 import formz/validation 6 6 import gleam/list 7 7 import gleeunit ··· 123 123 |> formz.parse 124 124 125 125 let assert [Element(field, _)] = formz.items(f) 126 - field |> should_be_field_with_error("Must be a whole number") 126 + field |> should_be_field_with_error("must be a whole number") 127 127 } 128 128 129 129 pub fn parse_double_field_form_with_error_test() { ··· 139 139 |> formz.parse 140 140 141 141 let assert [Element(fielda, _), Element(fieldb, _)] = formz.items(f) 142 - fielda |> should_be_field_with_error("Must be a whole number") 142 + fielda |> should_be_field_with_error("must be a whole number") 143 143 fieldb |> should_be_field_no_error 144 144 145 145 let assert Error(f) = ··· 149 149 150 150 let assert [Element(fielda, _), Element(fieldb, _)] = formz.items(f) 151 151 fielda |> should_be_field_no_error 152 - fieldb |> should_be_field_with_error("Must be a whole number") 152 + fieldb |> should_be_field_with_error("must be a whole number") 153 153 154 154 let assert Error(f) = 155 155 form ··· 157 157 |> formz.parse 158 158 159 159 let assert [Element(fielda, _), Element(fieldb, _)] = formz.items(f) 160 - fielda |> should_be_field_with_error("Must be a whole number") 161 - fieldb |> should_be_field_with_error("Must be a whole number") 160 + fielda |> should_be_field_with_error("must be a whole number") 161 + fieldb |> should_be_field_with_error("must be a whole number") 162 162 } 163 163 164 164 pub fn parse_triple_field_form_with_error_test() { ··· 177 177 formz.items(f) 178 178 fielda |> should_be_field_no_error 179 179 fieldb |> should_be_field_no_error 180 - fieldc |> should_be_field_with_error("Must be a whole number") 180 + fieldc |> should_be_field_with_error("must be a whole number") 181 181 182 182 let assert Error(f) = 183 183 form ··· 186 186 let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 187 187 formz.items(f) 188 188 fielda |> should_be_field_no_error 189 - fieldb |> should_be_field_with_error("Must be a whole number") 190 - fieldc |> should_be_field_with_error("Must be a whole number") 189 + fieldb |> should_be_field_with_error("must be a whole number") 190 + fieldc |> should_be_field_with_error("must be a whole number") 191 191 192 192 let assert Error(f) = 193 193 form ··· 196 196 let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 197 197 formz.items(f) 198 198 fielda |> should_be_field_no_error 199 - fieldb |> should_be_field_with_error("Must be a whole number") 199 + fieldb |> should_be_field_with_error("must be a whole number") 200 200 fieldc |> should_be_field_no_error 201 201 202 202 let assert Error(f) = ··· 205 205 |> formz.parse 206 206 let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 207 207 formz.items(f) 208 - fielda |> should_be_field_with_error("Must be a whole number") 209 - fieldb |> should_be_field_with_error("Must be a whole number") 208 + fielda |> should_be_field_with_error("must be a whole number") 209 + fieldb |> should_be_field_with_error("must be a whole number") 210 210 fieldc |> should_be_field_no_error 211 211 212 212 let assert Error(f) = ··· 215 215 |> formz.parse 216 216 let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 217 217 formz.items(f) 218 - fielda |> should_be_field_with_error("Must be a whole number") 218 + fielda |> should_be_field_with_error("must be a whole number") 219 219 fieldb |> should_be_field_no_error 220 220 fieldc |> should_be_field_no_error 221 221 } ··· 294 294 295 295 let f2 = 296 296 formz.new() 297 - |> formz.add_form(form_details("name"), f1) 297 + |> formz.add_form(subform("name"), f1) 298 298 |> formz.add(field("d"), integer_field()) 299 299 |> formz.decodes(fn(a) { fn(b) { #(a, b) } }) 300 300 ··· 319 319 320 320 let f2 = 321 321 formz.new() 322 - |> formz.add_form(form_details("name"), f1) 322 + |> formz.add_form(subform("name"), f1) 323 323 |> formz.add(field("d"), integer_field()) 324 324 |> formz.decodes(fn(a) { fn(b) { #(a, b) } }) 325 325 ··· 338 338 |> get_form_from_error_result 339 339 |> formz.items 340 340 341 - fielda |> should_be_field_with_error("Must be a whole number") 341 + fielda |> should_be_field_with_error("must be a whole number") 342 342 fieldb |> should_be_field_no_error 343 343 fieldc |> should_be_field_no_error 344 344 fieldd |> should_be_field_no_error
+19 -13
formz/test/formz/formz_use_test.gleam
··· 1 1 import formz/definition 2 2 import formz/field.{field} 3 - import formz/form_details.{form_details} 4 3 import formz/formz_use.{Element, Set} as formz 4 + import formz/subform.{subform} 5 + import gleam/string 5 6 6 7 import formz/validation 7 8 import gleeunit ··· 85 86 |> field.set_name("a") 86 87 |> field.set_label("A"), 87 88 text_field() 88 - |> definition.validates(validation.must_be_longer_than(3)), 89 + |> definition.validates(fn(str) { 90 + case string.length(str) > 3 { 91 + True -> Ok(str) 92 + False -> Error("must be longer than 3") 93 + } 94 + }), 89 95 ) 90 96 91 97 use b <- formz.with(field(named: "b"), integer_field()) ··· 195 201 |> formz.parse 196 202 197 203 let assert [Element(field, _)] = formz.items(f) 198 - field |> should_be_field_with_error("Must be a whole number") 204 + field |> should_be_field_with_error("must be a whole number") 199 205 } 200 206 201 207 pub fn parse_triple_field_form_with_error_test() { ··· 208 214 209 215 fielda |> should_be_field_no_error 210 216 fieldb |> should_be_field_no_error 211 - fieldc |> should_be_field_with_error("Must be a number") 217 + fieldc |> should_be_field_with_error("must be a number") 212 218 213 219 let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 214 220 three_field_form() ··· 217 223 |> get_form_from_error_result 218 224 |> formz.items 219 225 fielda |> should_be_field_no_error 220 - fieldb |> should_be_field_with_error("Must be a whole number") 221 - fieldc |> should_be_field_with_error("Must be a number") 226 + fieldb |> should_be_field_with_error("must be a whole number") 227 + fieldc |> should_be_field_with_error("must be a number") 222 228 223 229 let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = 224 230 three_field_form() ··· 227 233 |> get_form_from_error_result 228 234 |> formz.items 229 235 fielda |> should_be_field_no_error 230 - fieldb |> should_be_field_with_error("Must be a whole number") 236 + fieldb |> should_be_field_with_error("must be a whole number") 231 237 fieldc |> should_be_field_no_error 232 238 233 239 let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = ··· 236 242 |> formz.parse 237 243 |> get_form_from_error_result 238 244 |> formz.items 239 - fielda |> should_be_field_with_error("Must be longer than 3") 240 - fieldb |> should_be_field_with_error("Must be a whole number") 245 + fielda |> should_be_field_with_error("must be longer than 3") 246 + fieldb |> should_be_field_with_error("must be a whole number") 241 247 fieldc |> should_be_field_no_error 242 248 243 249 let assert [Element(fielda, _), Element(fieldb, _), Element(fieldc, _)] = ··· 246 252 |> formz.parse 247 253 |> get_form_from_error_result 248 254 |> formz.items 249 - fielda |> should_be_field_with_error("Must be longer than 3") 255 + fielda |> should_be_field_with_error("must be longer than 3") 250 256 fieldb |> should_be_field_no_error 251 257 fieldc |> should_be_field_no_error 252 258 } ··· 261 267 } 262 268 263 269 let f2 = { 264 - use a <- formz.with_form(form_details("name"), f1) 270 + use a <- formz.with_form(subform("name"), f1) 265 271 use b <- formz.with(field("d"), integer_field()) 266 272 267 273 formz.create_form(#(a, b)) ··· 288 294 } 289 295 290 296 let f2 = { 291 - use a <- formz.with_form(form_details("name"), f1) 297 + use a <- formz.with_form(subform("name"), f1) 292 298 use b <- formz.with(field("d"), integer_field()) 293 299 294 300 formz.create_form(#(a, b)) ··· 309 315 |> get_form_from_error_result 310 316 |> formz.items 311 317 312 - fielda |> should_be_field_with_error("Must be a whole number") 318 + fielda |> should_be_field_with_error("must be a whole number") 313 319 fieldb |> should_be_field_no_error 314 320 fieldc |> should_be_field_no_error 315 321 fieldd |> should_be_field_no_error
+37 -113
formz/test/formz/validation_test.gleam
··· 37 37 } 38 38 39 39 pub fn string_test() { 40 - "" |> validation.string |> should.equal(Error("Must not be empty")) 41 - " " |> validation.string |> should.equal(Error("Must not be empty")) 40 + "" |> validation.string |> should.equal(Ok("")) 41 + " " |> validation.string |> should.equal(Ok("")) 42 42 "a" |> validation.string |> should.equal(Ok("a")) 43 43 "b " |> validation.string |> should.equal(Ok("b")) 44 44 " c" |> validation.string |> should.equal(Ok("c")) 45 45 " d " |> validation.string |> should.equal(Ok("d")) 46 46 } 47 47 48 - pub fn must_be_longer_than_test() { 49 - "" 50 - |> validation.must_be_longer_than(2) 51 - |> should.equal(Error("Must be longer than 2")) 52 - "a" 53 - |> validation.must_be_longer_than(2) 54 - |> should.equal(Error("Must be longer than 2")) 55 - "ab" 56 - |> validation.must_be_longer_than(2) 57 - |> should.equal(Error("Must be longer than 2")) 58 - "abc" |> validation.must_be_longer_than(2) |> should.equal(Ok("abc")) 59 - "abcd" |> validation.must_be_longer_than(2) |> should.equal(Ok("abcd")) 60 - } 61 - 62 48 pub fn email_test() { 63 - "xxxxx" |> validation.email |> should.equal(Error("Must be an email address")) 49 + "xxxxx" |> validation.email |> should.equal(Error("must be an email address")) 64 50 "a@" |> validation.email |> should.equal(Ok("a@")) 65 51 "@a" |> validation.email |> should.equal(Ok("@a")) 66 52 "a@a" |> validation.email |> should.equal(Ok("a@a")) 67 53 } 68 54 69 - pub fn integer_test() { 70 - "" |> validation.int |> should.equal(Error("Must be a whole number")) 71 - "a" |> validation.int |> should.equal(Error("Must be a whole number")) 72 - "1.0" |> validation.int |> should.equal(Error("Must be a whole number")) 55 + pub fn int_test() { 56 + "" |> validation.int |> should.equal(Error("must be a whole number")) 57 + "a" |> validation.int |> should.equal(Error("must be a whole number")) 58 + "1.0" |> validation.int |> should.equal(Error("must be a whole number")) 73 59 "1" |> validation.int |> should.equal(Ok(1)) 74 60 } 75 61 76 62 pub fn number_test() { 77 - "" |> validation.number |> should.equal(Error("Must be a number")) 78 - "a" |> validation.number |> should.equal(Error("Must be a number")) 63 + "" |> validation.number |> should.equal(Error("must be a number")) 64 + "a" |> validation.number |> should.equal(Error("must be a number")) 79 65 "1.0" |> validation.number |> should.equal(Ok(1.0)) 80 66 "1" |> validation.number |> should.equal(Ok(1.0)) 81 67 } 82 68 83 - pub fn enum_test() { 84 - "x" 85 - |> validation.enum(variants() |> list.take(1)) 86 - |> should.equal(Error("Must be A")) 87 - "x" 88 - |> validation.enum(variants() |> list.take(2)) 89 - |> should.equal(Error("Must be one of A, B")) 90 - "x" 91 - |> validation.enum(variants() |> list.take(3)) 92 - |> should.equal(Error("Must be one of A, B, C")) 93 - "x" 94 - |> validation.enum(variants() |> list.take(4)) 95 - |> should.equal(Error("Must be one of A, B, C, D")) 96 - "x" 97 - |> validation.enum(variants()) 98 - |> should.equal(Error("Must be one of A, B, C, D...")) 99 - "A" |> validation.enum(variants()) |> should.equal(Ok(A)) 100 - "Y" |> validation.enum(variants()) |> should.equal(Ok(Y)) 101 - } 102 - 103 69 pub fn list_item_test() { 104 70 "x" 105 - |> validation.enum_by_index(variants() |> list.take(1)) 106 - |> should.equal(Error("Must be A")) 71 + |> validation.list_item_by_index(alphabet() |> list.take(1)) 72 + |> should.equal(Error("must be an item in list")) 107 73 "x" 108 - |> validation.enum_by_index(variants() |> list.take(2)) 109 - |> should.equal(Error("Must be one of A, B")) 74 + |> validation.list_item_by_index(alphabet() |> list.take(2)) 75 + |> should.equal(Error("must be an item in list")) 110 76 "x" 111 - |> validation.enum_by_index(variants() |> list.take(3)) 112 - |> should.equal(Error("Must be one of A, B, C")) 77 + |> validation.list_item_by_index(alphabet() |> list.take(3)) 78 + |> should.equal(Error("must be an item in list")) 113 79 "x" 114 - |> validation.enum_by_index(variants() |> list.take(4)) 115 - |> should.equal(Error("Must be one of A, B, C, D")) 80 + |> validation.list_item_by_index(alphabet() |> list.take(4)) 81 + |> should.equal(Error("must be an item in list")) 116 82 "x" 117 - |> validation.enum_by_index(variants()) 118 - |> should.equal(Error("Must be one of A, B, C, D...")) 119 - "0" |> validation.enum_by_index(variants()) |> should.equal(Ok(A)) 120 - "24" |> validation.enum_by_index(variants()) |> should.equal(Ok(Y)) 83 + |> validation.list_item_by_index(alphabet()) 84 + |> should.equal(Error("must be an item in list")) 85 + "0" |> validation.list_item_by_index(alphabet()) |> should.equal(Ok("A")) 86 + "24" |> validation.list_item_by_index(alphabet()) |> should.equal(Ok("Y")) 121 87 } 122 88 123 89 pub fn boolean_test() { 124 - "x" |> validation.boolean |> should.equal(Error("Must be true or false")) 90 + "x" |> validation.boolean |> should.equal(Error("must be true or false")) 125 91 "" |> validation.boolean |> should.equal(Ok(False)) 126 92 "true" |> validation.boolean |> should.equal(Ok(True)) 127 93 "false" |> validation.boolean |> should.equal(Ok(False)) ··· 131 97 "off" |> validation.boolean |> should.equal(Ok(False)) 132 98 } 133 99 134 - pub fn true_test() { 135 - "" |> validation.true |> should.equal(Error("Must be true")) 136 - "false" |> validation.true |> should.equal(Error("Must be true")) 137 - "False" |> validation.true |> should.equal(Error("Must be true")) 138 - "off" |> validation.true |> should.equal(Error("Must be true")) 139 - "true" |> validation.true |> should.equal(Ok(True)) 140 - "True" |> validation.true |> should.equal(Ok(True)) 141 - "on" |> validation.true |> should.equal(Ok(True)) 142 - } 143 - 144 100 pub fn and_test() { 101 + let is_even = fn(num) { 102 + case num % 2 == 0 { 103 + True -> Ok(num) 104 + False -> Error("must be even") 105 + } 106 + } 145 107 let v = 146 - validation.enum([#("off", "off"), #("yes", "yes")]) 147 - |> validation.and(validation.true) 108 + validation.and(validation.list_item_by_index([1, 2, 3, 5, 7, 9]), is_even) 148 109 149 - "" |> v |> should.equal(Error("Must be one of off, yes")) 150 - "\"on\"" |> v |> should.equal(Error("Must be one of off, yes")) 151 - "\"off\"" |> v |> should.equal(Error("Must be true")) 152 - "\"yes\"" |> v |> should.equal(Ok(True)) 153 - } 154 - 155 - pub fn or_test() { 156 - let v = 157 - validation.enum([#("off", "off"), #("yes", "yes")]) 158 - |> validation.or(validation.enum([#("on", "on"), #("true", "true")])) 159 - 160 - "" 161 - |> v 162 - |> should.equal(Error("Must be one of off, yes or Must be one of on, true")) 163 - "\"off\"" |> v |> should.equal(Ok("off")) 164 - "\"yes\"" |> v |> should.equal(Ok("yes")) 165 - "\"on\"" |> v |> should.equal(Ok("on")) 166 - "\"true\"" |> v |> should.equal(Ok("true")) 110 + "" |> v |> should.equal(Error("must be an item in list")) 111 + "10" |> v |> should.equal(Error("must be an item in list")) 112 + "x" |> v |> should.equal(Error("must be an item in list")) 113 + "0" |> v |> should.equal(Error("must be even")) 114 + "1" |> v |> should.equal(Ok(2)) 167 115 } 168 116 169 117 pub fn replace_error_test() { ··· 176 124 |> should.equal(Error("Uh oh!")) 177 125 } 178 126 179 - fn variants() { 127 + fn alphabet() { 180 128 [ 181 - #("A", A), 182 - #("B", B), 183 - #("C", C), 184 - #("D", D), 185 - #("E", E), 186 - #("F", F), 187 - #("G", G), 188 - #("H", H), 189 - #("I", I), 190 - #("J", J), 191 - #("K", K), 192 - #("L", L), 193 - #("M", M), 194 - #("N", N), 195 - #("O", O), 196 - #("P", P), 197 - #("Q", Q), 198 - #("R", R), 199 - #("S", S), 200 - #("T", T), 201 - #("U", U), 202 - #("V", V), 203 - #("W", W), 204 - #("X", X), 205 - #("Y", Y), 206 - #("Z", Z), 129 + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", 130 + "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 207 131 ] 208 132 }
+14 -6
formz_demo/src/formz_demo/examples/all_the_inputs.gleam
··· 1 + import formz/definition 1 2 import formz/field.{field} 2 3 import formz/formz_use as formz 3 4 import formz_string/definitions 5 + import formz_string/widgets 4 6 5 7 pub fn make_form() { 6 - let choices = [#("Yes", True), #("Maybe", True), #("No", False)] 7 - 8 8 use a <- formz.with(field("a"), definitions.text_field()) 9 9 use b <- formz.with(field("b"), definitions.integer_field()) 10 10 use c <- formz.with(field("c"), definitions.number_field()) 11 11 use d <- formz.with(field("d"), definitions.boolean_field()) 12 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(field("i"), definitions.list_field(["Dog", "Cat", "Ant"])) 13 + use f <- formz.with(field("f"), definitions.password_field()) 14 + use g <- formz.with( 15 + field("g"), 16 + definitions.choices_field(letters(), placeholder: A), 17 + ) 18 + use h <- formz.with(field("h"), definitions.list_field(["Dog", "Cat", "Ant"])) 19 + use i <- formz.with( 20 + field("i"), 21 + definitions.text_field() 22 + |> definition.set_widget(widgets.textarea_widget()), 23 + ) 16 24 17 - formz.create_form(#(a, b, c, d, e, f, g, h)) 25 + formz.create_form(#(a, b, c, d, e, f, g, h, i)) 18 26 } 19 27 20 28 pub type Alphabet {
+36 -16
formz_lustre/src/formz_lustre/definitions.gleam
··· 1 + //// Hopefully these are pretty self-explanatory. I haven't provided any 2 + //// examples here, as these are all used to build HTML forms and I don't know 3 + //// how helpful it would be to see the raw HTML. If you'd like to see 4 + //// examples of these in action, please see the [formz_demo](https://github.com/bentomas/formz/tree/main/formz_demo) 5 + //// example project. 6 + 1 7 import formz/definition.{Definition} 2 8 import formz/validation 3 9 import formz_lustre/widgets 10 + import gleam/int 4 11 import gleam/list 5 12 13 + /// Create a basic form input. Parsed as a String. 6 14 pub fn text_field() { 7 15 Definition(widgets.text_like_widget("text"), validation.string, "") 8 16 } 9 17 18 + /// Create an email form input. Parsed as a String but must 19 + /// look like an email address, i.e. the string has an `@`. 10 20 pub fn email_field() { 11 21 Definition(widgets.text_like_widget("email"), validation.email, "") 12 22 } 13 23 24 + /// Create a whole number form input. Parsed as an Int. 14 25 pub fn integer_field() { 15 26 Definition(widgets.text_like_widget("number"), validation.int, 0) 16 27 } 17 28 29 + /// Create a number form input. Parsed as a Float. 18 30 pub fn number_field() { 19 31 Definition(widgets.text_like_widget("number"), validation.number, 0.0) 20 32 } 21 33 34 + /// Create a checkbox form input. Parsed as a Boolean. 22 35 pub fn boolean_field() { 23 36 Definition(widgets.checkbox_widget(), validation.boolean, False) 24 37 } 25 38 39 + /// Create a password form input, which hides the input value. Parsed as a String 26 40 pub fn password_field() { 27 41 Definition(widgets.password_widget(), validation.string, "") 28 42 } 29 43 30 - pub fn enum_field(variants: List(#(String, enum))) { 31 - let assert Ok(#(_, first)) = list.first(variants) 32 - Definition( 33 - widgets.select_widget(variants), 34 - validation.enum(variants) 35 - |> validation.replace_error("Please select an option"), 36 - first, 37 - ) 38 - } 44 + /// Creates a `<select>` input. Takes a tuple of #(String, String) where the first 45 + /// item in the tuple is the label, and the second item can be any Gleam type and 46 + /// is the value that would be parsed for a given selection. 47 + /// 48 + /// Because of how you build `formz` forms, you need to provide a placeholder of 49 + /// the value type. Is this annoying? Would it be more or less annoying if I 50 + /// required a non-empty list for the variants instead? I'm not sure. Let me know! 51 + pub fn choices_field( 52 + variants: List(#(String, enum)), 53 + placeholder placeholder: enum, 54 + ) { 55 + let keys_indexed = 56 + variants 57 + |> list.index_map(fn(t, i) { #(t.0, int.to_string(i)) }) 39 58 40 - pub fn indexed_enum_field(variants: List(#(String, enum))) { 41 - let keys_indexed = list.index_map(variants, fn(t, i) { #(t.0, i) }) 42 - let assert Ok(#(_, first)) = list.first(variants) 59 + let values = variants |> list.map(fn(t) { t.1 }) 60 + 43 61 Definition( 44 62 widgets.select_widget(keys_indexed), 45 - validation.enum_by_index(variants) 63 + validation.list_item_by_index(values) 46 64 |> validation.replace_error("Please select an option"), 47 - first, 65 + placeholder, 48 66 ) 49 67 } 50 68 69 + /// Creates a `<select>` input from a list of strings. Validates that the parsed 70 + /// value is one of the strings in the list. 51 71 pub fn list_field(variants: List(String)) { 52 - let tuple_list = list.map(variants, fn(s) { #(s, s) }) 53 - indexed_enum_field(tuple_list) 72 + let labels_and_values = list.map(variants, fn(s) { #(s, s) }) 73 + choices_field(labels_and_values, "") 54 74 }
+3 -15
formz_lustre/src/formz_lustre/widgets.gleam
··· 1 1 import formz/field.{type Field} 2 2 import formz/widget 3 3 import gleam/list 4 - import gleam/string 5 4 import lustre/attribute 6 5 import lustre/element 7 6 import lustre/element/html ··· 106 105 } 107 106 } 108 107 109 - pub fn select_widget(variants: List(#(String, value))) { 108 + pub fn select_widget(variants: List(#(String, String))) { 110 109 fn(input: Field, args: widget.Args) -> element.Element(msg) { 111 110 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 111 [ 124 112 name_attr(input.name), 125 113 id_attr(args.id), ··· 128 116 list.flatten([ 129 117 [html.option([attribute.value("")], "Select..."), html.hr([])], 130 118 list.map(variants, fn(variant) { 131 - let val = string.inspect(variant.1) 119 + let val = variant.1 132 120 html.option( 133 - [value_attr(val), attribute.selected(input.value == val)], 121 + [attribute.value(val), attribute.selected(input.value == val)], 134 122 variant.0, 135 123 ) 136 124 }),
+19 -27
formz_lustre/test/formz_lustre/fields_test.gleam
··· 1 - import formz/definition 2 1 import gleeunit 3 2 import gleeunit/should 4 3 5 4 import formz_lustre/definitions 6 - import formz_string/definitions as string_kinds 5 + import formz_string/definitions as string_definitions 7 6 8 7 pub fn main() { 9 8 gleeunit.main() 10 9 } 11 10 12 - fn fields_should_be_equal_except_widget( 13 - field1: definition.Definition(format1, output), 14 - field2: definition.Definition(format2, output), 15 - ) { 16 - field1.placeholder |> should.equal(field2.placeholder) 17 - field1.transform |> should.equal(field2.transform) 18 - } 19 - 20 11 pub fn text_field_test() { 21 - let string_field = string_kinds.text_field() 12 + let string_field = string_definitions.text_field() 22 13 let field = definitions.text_field() 23 14 24 - fields_should_be_equal_except_widget(field, string_field) 15 + field.transform |> should.equal(string_field.transform) 25 16 } 26 17 27 18 pub fn email_field_test() { 28 - let string_field = string_kinds.email_field() 19 + let string_field = string_definitions.email_field() 29 20 let field = definitions.email_field() 30 21 31 - fields_should_be_equal_except_widget(field, string_field) 22 + field.transform |> should.equal(string_field.transform) 32 23 } 33 24 34 25 pub fn number_field_test() { 35 - let string_field = string_kinds.number_field() 26 + let string_field = string_definitions.number_field() 36 27 let field = definitions.number_field() 37 28 38 - fields_should_be_equal_except_widget(field, string_field) 29 + field.transform |> should.equal(string_field.transform) 39 30 } 40 31 41 32 pub fn integer_field_test() { 42 - let string_field = string_kinds.integer_field() 33 + let string_field = string_definitions.integer_field() 43 34 let field = definitions.integer_field() 44 35 45 - fields_should_be_equal_except_widget(field, string_field) 36 + field.transform |> should.equal(string_field.transform) 46 37 } 47 38 48 39 pub fn boolean_field_test() { 49 - let string_field = string_kinds.boolean_field() 40 + let string_field = string_definitions.boolean_field() 50 41 let field = definitions.boolean_field() 51 42 52 - fields_should_be_equal_except_widget(field, string_field) 43 + field.transform |> should.equal(string_field.transform) 53 44 } 54 45 55 - pub fn enum_field_test() { 56 - let string_field = string_kinds.enum_field([#("a", "A"), #("b", "B")]) 57 - let field = definitions.enum_field([#("a", "A"), #("b", "B")]) 46 + pub fn choices_field_test() { 47 + let string_field = 48 + string_definitions.choices_field([#("a", "A"), #("b", "B")], "") 49 + let field = definitions.choices_field([#("a", "A"), #("b", "B")], "") 58 50 59 - fields_should_be_equal_except_widget(field, string_field) 51 + field.transform |> should.equal(string_field.transform) 60 52 } 61 53 62 54 pub fn indexed_enum_field_test() { 63 - let string_field = string_kinds.indexed_enum_field([#("a", "A"), #("b", "B")]) 64 - let field = definitions.indexed_enum_field([#("a", "A"), #("b", "B")]) 55 + let string_field = string_definitions.list_field(["A", "B"]) 56 + let field = definitions.list_field(["A", "B"]) 65 57 66 - fields_should_be_equal_except_widget(field, string_field) 58 + field.transform |> should.equal(string_field.transform) 67 59 }
+36 -16
formz_nakai/src/formz_nakai/definitions.gleam
··· 1 + //// Hopefully these are pretty self-explanatory. I haven't provided any 2 + //// examples here, as these are all used to build HTML forms and I don't know 3 + //// how helpful it would be to see the raw HTML. If you'd like to see 4 + //// examples of these in action, please see the [formz_demo](https://github.com/bentomas/formz/tree/main/formz_demo) 5 + //// example project. 6 + 1 7 import formz/definition.{Definition} 2 8 import formz/validation 3 9 import formz_nakai/widgets 10 + import gleam/int 4 11 import gleam/list 5 12 13 + /// Create a basic form input. Parsed as a String. 6 14 pub fn text_field() { 7 15 Definition(widgets.text_like_widget("text"), validation.string, "") 8 16 } 9 17 18 + /// Create an email form input. Parsed as a String but must 19 + /// look like an email address, i.e. the string has an `@`. 10 20 pub fn email_field() { 11 21 Definition(widgets.text_like_widget("email"), validation.email, "") 12 22 } 13 23 24 + /// Create a whole number form input. Parsed as an Int. 14 25 pub fn integer_field() { 15 26 Definition(widgets.text_like_widget("number"), validation.int, 0) 16 27 } 17 28 29 + /// Create a number form input. Parsed as a Float. 18 30 pub fn number_field() { 19 31 Definition(widgets.text_like_widget("number"), validation.number, 0.0) 20 32 } 21 33 34 + /// Create a checkbox form input. Parsed as a Boolean. 22 35 pub fn boolean_field() { 23 36 Definition(widgets.checkbox_widget(), validation.boolean, False) 24 37 } 25 38 39 + /// Create a password form input, which hides the input value. Parsed as a String 26 40 pub fn password_field() { 27 41 Definition(widgets.password_widget(), validation.string, "") 28 42 } 29 43 30 - pub fn enum_field(variants: List(#(String, enum))) { 31 - let assert Ok(#(_, first)) = list.first(variants) 32 - Definition( 33 - widgets.select_widget(variants), 34 - validation.enum(variants) 35 - |> validation.replace_error("Please select an option"), 36 - first, 37 - ) 38 - } 44 + /// Creates a `<select>` input. Takes a tuple of #(String, String) where the first 45 + /// item in the tuple is the label, and the second item can be any Gleam type and 46 + /// is the value that would be parsed for a given selection. 47 + /// 48 + /// Because of how you build `formz` forms, you need to provide a placeholder of 49 + /// the value type. Is this annoying? Would it be more or less annoying if I 50 + /// required a non-empty list for the variants instead? I'm not sure. Let me know! 51 + pub fn choices_field( 52 + variants: List(#(String, enum)), 53 + placeholder placeholder: enum, 54 + ) { 55 + let keys_indexed = 56 + variants 57 + |> list.index_map(fn(t, i) { #(t.0, int.to_string(i)) }) 39 58 40 - pub fn indexed_enum_field(variants: List(#(String, enum))) { 41 - let keys_indexed = list.index_map(variants, fn(t, i) { #(t.0, i) }) 42 - let assert Ok(#(_, first)) = list.first(variants) 59 + let values = variants |> list.map(fn(t) { t.1 }) 60 + 43 61 Definition( 44 62 widgets.select_widget(keys_indexed), 45 - validation.enum_by_index(variants) 63 + validation.list_item_by_index(values) 46 64 |> validation.replace_error("Please select an option"), 47 - first, 65 + placeholder, 48 66 ) 49 67 } 50 68 69 + /// Creates a `<select>` input from a list of strings. Validates that the parsed 70 + /// value is one of the strings in the list. 51 71 pub fn list_field(variants: List(String)) { 52 - let tuple_list = list.map(variants, fn(s) { #(s, s) }) 53 - indexed_enum_field(tuple_list) 72 + let labels_and_values = list.map(variants, fn(s) { #(s, s) }) 73 + choices_field(labels_and_values, "") 54 74 }
+2 -3
formz_nakai/src/formz_nakai/widgets.gleam
··· 2 2 import formz/widget 3 3 4 4 import gleam/list 5 - import gleam/string 6 5 import nakai/attr 7 6 import nakai/html 8 7 ··· 118 117 } 119 118 } 120 119 121 - pub fn select_widget(variants: List(#(String, value))) { 120 + pub fn select_widget(variants: List(#(String, String))) { 122 121 fn(field: Field, args: widget.Args) -> html.Node { 123 122 html.select( 124 123 list.flatten([ ··· 129 128 list.flatten([ 130 129 [html.option([attr.value("")], [html.Text("Select...")]), html.hr([])], 131 130 list.map(variants, fn(variant) { 132 - let val = string.inspect(variant.1) 131 + let val = variant.1 133 132 let selected_attr = case field.value == val { 134 133 True -> [attr.selected()] 135 134 _ -> []
+19 -27
formz_nakai/test/formz_nakai/fields_test.gleam
··· 1 - import formz/definition 2 1 import gleeunit 3 2 import gleeunit/should 4 3 5 4 import formz_nakai/definitions 6 - import formz_string/definitions as string_kinds 5 + import formz_string/definitions as string_definitions 7 6 8 7 pub fn main() { 9 8 gleeunit.main() 10 9 } 11 10 12 - fn fields_should_be_equal_except_widget( 13 - field1: definition.Definition(format1, output), 14 - field2: definition.Definition(format2, output), 15 - ) { 16 - field1.placeholder |> should.equal(field2.placeholder) 17 - field1.transform |> should.equal(field2.transform) 18 - } 19 - 20 11 pub fn text_field_test() { 21 - let string_field = string_kinds.text_field() 12 + let string_field = string_definitions.text_field() 22 13 let field = definitions.text_field() 23 14 24 - fields_should_be_equal_except_widget(field, string_field) 15 + field.transform |> should.equal(string_field.transform) 25 16 } 26 17 27 18 pub fn email_field_test() { 28 - let string_field = string_kinds.email_field() 19 + let string_field = string_definitions.email_field() 29 20 let field = definitions.email_field() 30 21 31 - fields_should_be_equal_except_widget(field, string_field) 22 + field.transform |> should.equal(string_field.transform) 32 23 } 33 24 34 25 pub fn number_field_test() { 35 - let string_field = string_kinds.number_field() 26 + let string_field = string_definitions.number_field() 36 27 let field = definitions.number_field() 37 28 38 - fields_should_be_equal_except_widget(field, string_field) 29 + field.transform |> should.equal(string_field.transform) 39 30 } 40 31 41 32 pub fn integer_field_test() { 42 - let string_field = string_kinds.integer_field() 33 + let string_field = string_definitions.integer_field() 43 34 let field = definitions.integer_field() 44 35 45 - fields_should_be_equal_except_widget(field, string_field) 36 + field.transform |> should.equal(string_field.transform) 46 37 } 47 38 48 39 pub fn boolean_field_test() { 49 - let string_field = string_kinds.boolean_field() 40 + let string_field = string_definitions.boolean_field() 50 41 let field = definitions.boolean_field() 51 42 52 - fields_should_be_equal_except_widget(field, string_field) 43 + field.transform |> should.equal(string_field.transform) 53 44 } 54 45 55 - pub fn enum_field_test() { 56 - let string_field = string_kinds.enum_field([#("a", "A"), #("b", "B")]) 57 - let field = definitions.enum_field([#("a", "A"), #("b", "B")]) 46 + pub fn choices_field_test() { 47 + let string_field = 48 + string_definitions.choices_field([#("a", "A"), #("b", "B")], "") 49 + let field = definitions.choices_field([#("a", "A"), #("b", "B")], "") 58 50 59 - fields_should_be_equal_except_widget(field, string_field) 51 + field.transform |> should.equal(string_field.transform) 60 52 } 61 53 62 54 pub fn indexed_enum_field_test() { 63 - let string_field = string_kinds.indexed_enum_field([#("a", "A"), #("b", "B")]) 64 - let field = definitions.indexed_enum_field([#("a", "A"), #("b", "B")]) 55 + let string_field = string_definitions.list_field(["A", "B"]) 56 + let field = definitions.list_field(["A", "B"]) 65 57 66 - fields_should_be_equal_except_widget(field, string_field) 58 + field.transform |> should.equal(string_field.transform) 67 59 }
+36 -16
formz_string/src/formz_string/definitions.gleam
··· 1 + //// Hopefully these are pretty self-explanatory. I haven't provided any 2 + //// examples here, as these are all used to build HTML forms and I don't know 3 + //// how helpful it would be to see the raw HTML. If you'd like to see 4 + //// examples of these in action, please see the [formz_demo](https://github.com/bentomas/formz/tree/main/formz_demo) 5 + //// example project. 6 + 1 7 import formz/definition.{Definition} 2 8 import formz/validation 3 9 import formz_string/widgets 10 + import gleam/int 4 11 import gleam/list 5 12 13 + /// Create a basic form input. Parsed as a String. 6 14 pub fn text_field() { 7 15 Definition(widgets.text_like_widget("text"), validation.string, "") 8 16 } 9 17 18 + /// Create an email form input. Parsed as a String but must 19 + /// look like an email address, i.e. the string has an `@`. 10 20 pub fn email_field() { 11 21 Definition(widgets.text_like_widget("email"), validation.email, "") 12 22 } 13 23 24 + /// Create a whole number form input. Parsed as an Int. 14 25 pub fn integer_field() { 15 26 Definition(widgets.text_like_widget("number"), validation.int, 0) 16 27 } 17 28 29 + /// Create a number form input. Parsed as a Float. 18 30 pub fn number_field() { 19 31 Definition(widgets.text_like_widget("number"), validation.number, 0.0) 20 32 } 21 33 34 + /// Create a checkbox form input. Parsed as a Boolean. 22 35 pub fn boolean_field() { 23 36 Definition(widgets.checkbox_widget(), validation.boolean, False) 24 37 } 25 38 39 + /// Create a password form input, which hides the input value. Parsed as a String 26 40 pub fn password_field() { 27 41 Definition(widgets.password_widget(), validation.string, "") 28 42 } 29 43 30 - pub fn enum_field(variants: List(#(String, enum))) { 31 - let assert Ok(#(_, first)) = list.first(variants) 32 - Definition( 33 - widgets.select_widget(variants), 34 - validation.enum(variants) 35 - |> validation.replace_error("Please select an option"), 36 - first, 37 - ) 38 - } 44 + /// Creates a `<select>` input. Takes a tuple of #(String, String) where the first 45 + /// item in the tuple is the label, and the second item can be any Gleam type and 46 + /// is the value that would be parsed for a given selection. 47 + /// 48 + /// Because of how you build `formz` forms, you need to provide a placeholder of 49 + /// the value type. Is this annoying? Would it be more or less annoying if I 50 + /// required a non-empty list for the variants instead? I'm not sure. Let me know! 51 + pub fn choices_field( 52 + variants: List(#(String, enum)), 53 + placeholder placeholder: enum, 54 + ) { 55 + let keys_indexed = 56 + variants 57 + |> list.index_map(fn(t, i) { #(t.0, int.to_string(i)) }) 39 58 40 - pub fn indexed_enum_field(variants: List(#(String, enum))) { 41 - let keys_indexed = list.index_map(variants, fn(t, i) { #(t.0, i) }) 42 - let assert Ok(#(_, first)) = list.first(variants) 59 + let values = variants |> list.map(fn(t) { t.1 }) 60 + 43 61 Definition( 44 62 widgets.select_widget(keys_indexed), 45 - validation.enum_by_index(variants) 63 + validation.list_item_by_index(values) 46 64 |> validation.replace_error("Please select an option"), 47 - first, 65 + placeholder, 48 66 ) 49 67 } 50 68 69 + /// Creates a `<select>` input from a list of strings. Validates that the parsed 70 + /// value is one of the strings in the list. 51 71 pub fn list_field(variants: List(String)) { 52 - let tuple_list = list.map(variants, fn(s) { #(s, s) }) 53 - indexed_enum_field(tuple_list) 72 + let labels_and_values = list.map(variants, fn(s) { #(s, s) }) 73 + choices_field(labels_and_values, "") 54 74 }
+1 -3
formz_string/src/formz_string/simple.gleam
··· 32 32 " <span class=\"formz_help_text\">" <> f.help_text <> " </span>" 33 33 } 34 34 let widget_el = 35 - " <span class=\"formz_widget\">" 36 - <> widget( 35 + widget( 37 36 f, 38 37 widget.Args( 39 38 id: f.name, ··· 41 40 described_by: widget.DescribedByNone, 42 41 ), 43 42 ) 44 - <> "</span>" 45 43 46 44 let errors_el = case f { 47 45 field.Valid(..) -> ""
+2 -2
formz_string/src/formz_string/widgets.gleam
··· 119 119 } 120 120 } 121 121 122 - pub fn select_widget(variants: List(#(String, value))) { 122 + pub fn select_widget(variants: List(#(String, String))) { 123 123 fn(field: Field, args: widget.Args) { 124 124 let choices = 125 125 list.map(variants, fn(variant) { 126 - let val = string.inspect(variant.1) 126 + let val = variant.1 127 127 let selected_attr = case field.value == val { 128 128 True -> " selected" 129 129 _ -> ""