-module(formz). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([create_form/1, decode/1, items/1, definition/3, definition_with_custom_optional/5, verify/2, widget/2, get_parse/1, get_optional_parse/1, named/1, set_name/2, set_label/2, set_help_text/2, set_disabled/2, make_disabled/1, limited_list/4, list/3, simple_limit_check/3, limit_at_least/1, limit_at_most/1, limit_between/2, data/2, field/3, required_field/3, subform/3, decode_then_try/2, get/2, validate/2, validate_all/1, update/3, update_config/3, field_error/3, listfield_errors/3, get_states/1]). -export_type([form/2, item/1, input_state/0, requirement/0, config/0, definition/3, list_parsing_result/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " A form is a list of fields and a decoder function. This module uses a\n" " series of callbacks to construct the decoder function as the fields are\n" " being added to the form. The idea is that you'd have a function that\n" " makes the form using the `use` syntax, and then be able to use the form\n" " later for parsing or rendering in different contexts.\n" "\n" " You can use this cheatsheet to navigate the module documentation:\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "
Creating a form\n" " create_form
\n" " required_field
\n" " field
\n" " list
\n" " limited_list
\n" " subform\n" "
Decoding and validating a form\n" " data
\n" " decode
\n" " decode_then_try
\n" " validate
\n" " validate_all\n" "
Creating a field definition\n" " definition
\n" " definition_with_custom_optional
\n" " verify
\n" " widget\n" "
Creating a limited list\n" " limit_at_least
\n" " limit_at_most
\n" " limit_between
\n" " simple_limit_check\n" "
Accessing and manipulating form items\n" " get
\n" " items
\n" " field_error
\n" " listfield_errors
\n" " update
\n" "
Accessing and manipulating config for a form item\n" " update_config
\n" " set_name
\n" " set_label
\n" " set_help_text
\n" " set_disabled
\n" " make_disabled
\n" "
\n" "\n" " ### Examples\n" "\n" " ```gleam\n" " fn make_form() {\n" " use name <- formz.required_field(field(\"name\"), definitions.text_field())\n" "\n" " formz.create_form(name)\n" " }\n" "\n" " fn process_form() {\n" " make_form()\n" " |> formz.data([#(\"name\", \"Louis\"))])\n" " |> formz.decode\n" " # -> Ok(\"Louis\")\n" " }\n" " ```\n" "\n" " ```gleam\n" " fn make_form() {\n" " use greeting <- field(field(\"greeting\"), definitions.text_field())\n" " use name <- field(field(\"name\"), definitions.text_field())\n" "\n" " formz.create_form(greeting <> \" \" <> name)\n" " }\n" "\n" " fn process_form() {\n" " make_form()\n" " |> data([#(\"greeting\", \"Hello\"), #(\"name\", \"World\")])\n" " |> formz.decode\n" " # -> Ok(\"Hello World\")\n" " }\n" " ```\n" ). -opaque form(FYC, FYD) :: {form, list(item(FYC)), fun((list(item(FYC))) -> {ok, FYD} | {error, list(item(FYC))}), FYD}. -type item(FYE) :: {field, config(), input_state(), FYE} | {list_field, config(), list(input_state()), fun((integer()) -> {ok, list(input_state())} | {error, integer()}), FYE} | {sub_form, config(), list(item(FYE))}. -type input_state() :: {unvalidated, binary(), requirement()} | {valid, binary(), requirement()} | {invalid, binary(), requirement(), binary()}. -type requirement() :: optional | required. -type config() :: {config, binary(), binary(), binary(), boolean()}. -opaque definition(FYF, FYG, FYH) :: {definition, FYF, fun((binary()) -> {ok, FYG} | {error, binary()}), FYG, fun((fun((binary()) -> {ok, FYG} | {error, binary()}), binary()) -> {ok, FYH} | {error, binary()}), FYH}. -type list_parsing_result(FYI) :: {list_parsing_result, binary(), requirement(), {ok, FYI} | {error, binary()}}. -file("src/formz.gleam", 323). ?DOC( " Create an empty form that \"decodes\" directly to `thing`. This is\n" " intended to be the final return value of a chain of callbacks that adds\n" " the form's fields.\n" "\n" " ```gleam\n" " create_form(1)\n" " |> decode\n" " # -> Ok(1)\n" " ```\n" " ```gleam\n" " fn make_form() {\n" " use field1 <- formz.required_field(field(\"field1\"), definitions.text_field())\n" " use field2 <- formz.required_field(field(\"field2\"), definitions.text_field())\n" " use field3 <- formz.required_field(field(\"field3\"), definitions.text_field())\n" "\n" " formz.create_form(#(field1, field2, field3))\n" " }\n" " ```\n" ). -spec create_form(FYM) -> form(any(), FYM). create_form(Thing) -> {form, [], fun(_) -> {ok, Thing} end, Thing}. -file("src/formz.gleam", 618). -spec state_from_parse_result(list_parsing_result(any())) -> input_state(). state_from_parse_result(Result) -> {list_parsing_result, Value, Requirement, Output} = Result, case Output of {ok, _} -> {valid, Value, Requirement}; {error, Error} -> {invalid, Value, Requirement, Error} end. -file("src/formz.gleam", 639). -spec parse_list_state( input_state(), fun((binary()) -> {ok, GAS} | {error, binary()}), GAS ) -> list_parsing_result(GAS). parse_list_state(State, Parse, Stub) -> _pipe = case {erlang:element(2, State), erlang:element(3, State)} of {<<""/utf8>>, optional} -> {ok, Stub}; {_, _} -> Parse(erlang:element(2, State)) end, {list_parsing_result, erlang:element(2, State), erlang:element(3, State), _pipe}. -file("src/formz.gleam", 651). -spec mark_above_max_values_as_invalid( list(list_parsing_result(GAW)), fun((integer()) -> {ok, list(input_state())} | {error, integer()}) ) -> list(list_parsing_result(GAW)). mark_above_max_values_as_invalid(List, Limit_check) -> Count = gleam@list:fold( List, 0, fun(Acc, R) -> case {erlang:element(2, R), erlang:element(3, R)} of {<<""/utf8>>, optional} -> Acc; {_, _} -> Acc + 1 end end ), case Limit_check(Count) of {ok, _} -> List; {error, Num_too_many} -> Max_index = Count - Num_too_many, {Mapped_results, _} = gleam@list:fold( List, {[], 0}, fun(Acc@1, R@1) -> {Results, I} = Acc@1, case {erlang:element(2, R@1), erlang:element(3, R@1), I >= Max_index} of {<<""/utf8>>, optional, _} -> {[R@1 | Results], I}; {_, _, false} -> {[R@1 | Results], I + 1}; {V, R@2, true} -> {[{list_parsing_result, V, R@2, {error, <<"exceeds maximum allowed items"/utf8>>}} | Results], I + 1} end end ), _pipe = Mapped_results, lists:reverse(_pipe) end. -file("src/formz.gleam", 861). ?DOC( " Decode the form. This means step through the fields one by one, parsing\n" " them individually. If any field fails to parse, the whole form is considered\n" " invalid, however it will still continue parsing the rest of the fields to\n" " collect all errors. This is useful for showing all errors at once. If no\n" " fields fail to parse, the decoded value is returned, which is the value given\n" " to `create_form`.\n" "\n" " If you'd like to decode the form but not get the output, so you can give\n" " feedback to a user in response to input, you can use `validate` or `validate_all`.\n" ). -spec decode(form(GCS, GCT)) -> {ok, GCT} | {error, form(GCS, GCT)}. decode(Form) -> case (erlang:element(3, Form))(erlang:element(2, Form)) of {ok, Output} -> {ok, Output}; {error, Items} -> {error, begin _record = Form, {form, Items, erlang:element(3, _record), erlang:element(4, _record)} end} end. -file("src/formz.gleam", 980). ?DOC( " Get each [`Item`](https://hexdocs.pm/formz/formz.html#Item) added\n" " to the form. Any time a field, list field, or subform are added, a `Item`\n" " is created. Use this to loop through all the fields of your form and\n" " generate HTML for them.\n" ). -spec items(form(GEH, any())) -> list(item(GEH)). items(Form) -> erlang:element(2, Form). -file("src/formz.gleam", 1107). -spec get_item_name(item(any())) -> binary(). get_item_name(Item) -> case Item of {field, Field, _, _} -> erlang:element(2, Field); {list_field, Field@1, _, _, _} -> erlang:element(2, Field@1); {sub_form, Subform, _} -> erlang:element(2, Subform) end. -file("src/formz.gleam", 1118). ?DOC( " Create a simple `Definition` that is parsed as an `Option` if the field\n" " is empty. See [formz_string](https://hexdocs.pm/formz_string/formz_string/definitions.html)\n" " for more examples of making widgets and definitions.\n" ). -spec definition(GGF, fun((binary()) -> {ok, GGG} | {error, binary()}), GGG) -> definition(GGF, GGG, gleam@option:option(GGG)). definition(Widget, Parse, Stub) -> {definition, Widget, Parse, Stub, fun(Fun, Str) -> case Str of <<""/utf8>> -> {ok, none}; _ -> _pipe = Fun(Str), gleam@result:map(_pipe, fun(Field@0) -> {some, Field@0} end) end end, none}. -file("src/formz.gleam", 1147). ?DOC( " Create a `Definition` that can parse to any type if the field is optional.\n" " This takes two functions. The first, `parse`, is the \"required\" parse\n" " function, which takes the raw string value, and turns it into the required\n" " type. The second, `optional_parse`, is a function that takes the normal\n" " parse function and the raw string value, and it is supposed to check the\n" " input string: if it is empty, return an `Ok` with a value of the optional\n" " type; and if it's not empty use the normal parse function.\n" "\n" " See [formz_string](https://hexdocs.pm/formz_string/formz_string/definitions.html)\n" " for more examples of making widgets and definitions.\n" ). -spec definition_with_custom_optional( GGK, fun((binary()) -> {ok, GGL} | {error, binary()}), GGL, fun((fun((binary()) -> {ok, GGL} | {error, binary()}), binary()) -> {ok, GGQ} | {error, binary()}), GGQ ) -> definition(GGK, GGL, GGQ). definition_with_custom_optional( Widget, Parse, Stub, Optional_parse, Optional_stub ) -> {definition, Widget, Parse, Stub, Optional_parse, Optional_stub}. -file("src/formz.gleam", 1176). ?DOC( " Chain additional validation onto the `parse` function. This is\n" " useful if you don't need to change the returned type, but might have\n" " additional constraints. Like say, requiring a `String` to be at least\n" " a certain length, or that an Int must be positive.\n" "\n" " ### Example\n" " ```gleam\n" " field\n" " |> validate(fn(i) {\n" " case i > 0 {\n" " True -> Ok(i)\n" " False -> Error(\"must be positive\")\n" " }\n" " }),\n" " ```\n" ). -spec verify( definition(GGW, GGX, GGY), fun((GGX) -> {ok, GGX} | {error, binary()}) ) -> definition(GGW, GGX, GGY). verify(Def, Fun) -> _record = Def, {definition, erlang:element(2, _record), fun(Val) -> _pipe = Val, _pipe@1 = (erlang:element(3, Def))(_pipe), gleam@result:'try'(_pipe@1, Fun) end, erlang:element(4, _record), erlang:element( 5, _record ), erlang:element(6, _record)}. -file("src/formz.gleam", 1222). ?DOC(" Update the widget of a definition.\n"). -spec widget(definition(GHH, GHI, GHJ), GHH) -> definition(GHH, GHI, GHJ). widget(Def, Widget) -> _record = Def, {definition, Widget, erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record), erlang:element(6, _record)}. -file("src/formz.gleam", 1230). ?DOC(false). -spec get_parse(definition(any(), GHR, any())) -> fun((binary()) -> {ok, GHR} | {error, binary()}). get_parse(Def) -> erlang:element(3, Def). -file("src/formz.gleam", 1237). ?DOC(false). -spec get_optional_parse(definition(any(), GHZ, GIA)) -> fun((fun((binary()) -> {ok, GHZ} | {error, binary()}), binary()) -> {ok, GIA} | {error, binary()}). get_optional_parse(Def) -> erlang:element(5, Def). -file("src/formz.gleam", 1271). ?DOC( " Create a field with the given name.\n" "\n" " It uses\n" " [justin.sentence_case](https://hexdocs.pm/justin/justin.html#sentence_case)\n" " to create an initial label. You can override the label with the `set_label`\n" " function. I don't know if this is very english-centric, so let me know if\n" " this is a bad experience in other languages and I'll consider\n" " something else.\n" "\n" " ```gleam\n" " field(\"name\")\n" " |> set_label(\"Full Name\")\n" " ```\n" ). -spec named(binary()) -> config(). named(Name) -> {config, Name, justin:sentence_case(Name), <<""/utf8>>, false}. -file("src/formz.gleam", 1281). ?DOC(" Set the name of the field. This is the key that will be used for the data.\n"). -spec set_name(config(), binary()) -> config(). set_name(Config, Name) -> _record = Config, {config, Name, erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record)}. -file("src/formz.gleam", 708). -spec add_prefix_to_item(item(GBN), binary()) -> item(GBN). add_prefix_to_item(Item, Prefix) -> case Item of {field, Item_details, State, Widget} -> New_name = <<<>/binary, (erlang:element(2, Item_details))/binary>>, {field, begin _pipe = Item_details, set_name(_pipe, New_name) end, State, Widget}; {list_field, Item_details@1, States, Limit_check, Widget@1} -> New_name@1 = <<<>/binary, (erlang:element(2, Item_details@1))/binary>>, {list_field, begin _pipe@1 = Item_details@1, set_name(_pipe@1, New_name@1) end, States, Limit_check, Widget@1}; {sub_form, Item_details@2, Sub_items} -> New_name@2 = <<<>/binary, (erlang:element(2, Item_details@2))/binary>>, {sub_form, begin _pipe@2 = Item_details@2, set_name(_pipe@2, New_name@2) end, Sub_items} end. -file("src/formz.gleam", 1287). ?DOC( " Set the label of the field. This is the primary text that will be displayed\n" " to the user about the field.\n" ). -spec set_label(config(), binary()) -> config(). set_label(Config, Label) -> _record = Config, {config, erlang:element(2, _record), Label, erlang:element(4, _record), erlang:element(5, _record)}. -file("src/formz.gleam", 1292). ?DOC(" Additional instructions or help text to display to the user about the field.\n"). -spec set_help_text(config(), binary()) -> config(). set_help_text(Config, Help_text) -> _record = Config, {config, erlang:element(2, _record), erlang:element(3, _record), Help_text, erlang:element(5, _record)}. -file("src/formz.gleam", 1297). ?DOC(" Set the `disabled` flag directly.\n"). -spec set_disabled(config(), boolean()) -> config(). set_disabled(Config, Disabled) -> _record = Config, {config, erlang:element(2, _record), erlang:element(3, _record), erlang:element(4, _record), Disabled}. -file("src/formz.gleam", 1306). ?DOC( " Mark the form `Item` as disabled. This will prevent the user from\n" " interacting with the field. If you do this for a subform, it will\n" " only work if the form generator renders the subform as a `fieldset`.\n" " For example, HTML does not allow you to mark inputs in a `
`\n" " disabled as group but you can do this with a `
`.\n" ). -spec make_disabled(config()) -> config(). make_disabled(Config) -> set_disabled(Config, true). -file("src/formz.gleam", 535). ?DOC( " Add a list field to a form, but with limits on the number of values that\n" " can be submitted. The `limit_check` function is used to impose those\n" " limits, and the `limit_at_least`, `limit_at_most`, and `limit_between`\n" " functions help you create this function for the most likely scenarios.\n" "\n" " The final argument is a callback that will be called when the form\n" " is being... constructed to look for more fields; validated to check for\n" " errors; and decoded to parse the input data. **For this reason, the\n" " callback should be a function without side effects.** It can be called any\n" " number of times. Don't do anything but create the type with the data you\n" " need! If you need to do decoding that has side effects, you should use\n" " `decode_then_try`.\n" "\n" "### Example\n" "\n" " ```gleam\n" " fn make_form() {\n" " use names <- formz.limited_list(formz.limit_at_most(4), field(\"name\"), definitions.text_field())\n" " // names is a List(String)\n" " formz.create_form(name)\n" " }\n" ). -spec limited_list( fun((integer()) -> {ok, list(input_state())} | {error, integer()}), config(), definition(FZY, FZZ, any()), fun((list(FZZ)) -> form(FZY, GAF)) ) -> form(FZY, GAF). limited_list(Limit_check, Config, Definition, Next) -> Next_form = Next([erlang:element(4, Definition)]), Initial_fields = begin _pipe = Limit_check(0), gleam@result:unwrap(_pipe, []) end, Updated_items = [{list_field, Config, Initial_fields, Limit_check, erlang:element(2, Definition)} | erlang:element(2, Next_form)], Decode = fun(Items) -> [{list_field, Field, States, Limit_check@1, Widget} | Next_items] = case Items of [{list_field, _, _, _, _} | _] -> Items; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"formz"/utf8>>, function => <<"limited_list"/utf8>>, line => 558}) end, Item_results = begin _pipe@1 = States, _pipe@2 = gleam@list:map( _pipe@1, fun(_capture) -> parse_list_state( _capture, erlang:element(3, Definition), erlang:element(4, Definition) ) end ), mark_above_max_values_as_invalid(_pipe@2, Limit_check@1) end, Item_outputs = begin _pipe@3 = Item_results, _pipe@4 = outputs_for_required_or_nonempty(_pipe@3), gleam@result:all(_pipe@4) end, Next_form@1 = Next( begin _pipe@5 = Item_outputs, gleam@result:unwrap(_pipe@5, [erlang:element(4, Definition)]) end ), Form_output = (erlang:element(3, Next_form@1))(Next_items), case {Form_output, Item_outputs} of {{ok, _}, {ok, _}} -> Form_output; {{error, Error_items}, {ok, _}} -> States@1 = begin _pipe@6 = States, gleam@list:map( _pipe@6, fun(S) -> {valid, erlang:element(2, S), erlang:element(3, S)} end ) end, F = {list_field, Field, States@1, Limit_check@1, Widget}, {error, [F | Error_items]}; {{ok, _}, {error, _}} -> States@2 = gleam@list:map( Item_results, fun state_from_parse_result/1 ), F@1 = {list_field, Field, States@2, Limit_check@1, Widget}, {error, [F@1 | mark_all_fields_as_valid(Next_items)]}; {{error, Error_items@1}, {error, _}} -> States@3 = gleam@list:map( Item_results, fun state_from_parse_result/1 ), F@2 = {list_field, Field, States@3, Limit_check@1, Widget}, {error, [F@2 | Error_items@1]} end end, {form, Updated_items, Decode, erlang:element(4, Next_form)}. -file("src/formz.gleam", 700). ?DOC( " Add a list field to a form, but with no limits on the number of values that\n" " can be submitted. A list field is like a normal field except a consumer can\n" " submit multiple values, and it will return a `List` of the parsed values.\n" "\n" " The final argument is a callback that will be called when the form\n" " is being... constructed to look for more fields; validated to check for\n" " errors; and decoded to parse the input data. **For this reason, the\n" " callback should be a function without side effects.** It can be called any\n" " number of times. Don't do anything but create the type with the data you\n" " need! If you need to do decoding that has side effects, you should use\n" " `decode_then_try`.\n" ). -spec list( config(), definition(GBB, GBC, any()), fun((list(GBC)) -> form(GBB, GBI)) ) -> form(GBB, GBI). list(Config, Definition, Next) -> limited_list(limit_at_most(1000000), Config, Definition, Next). -file("src/formz.gleam", 461). ?DOC( " Convenience function for creating a `LimitCheck` function. This takes\n" " the minimum number of required values, the maximum number of allowed values,\n" " and the number of \"extra\" blank inputs that should be offered to the user\n" " for filling out.\n" "\n" " If \"extra\" is 0, (say, to manage blank fields via javascript), then this\n" " will show 1 blank field initially.\n" ). -spec simple_limit_check(integer(), integer(), integer()) -> fun((integer()) -> {ok, list(input_state())} | {error, integer()}). simple_limit_check(Min, Max, Extra) -> fun(Num_nonempty) -> case Max - Num_nonempty of X when X < 0 -> {error, - X}; _ -> gleam@int:max( 1 - Num_nonempty, gleam@int:min(Extra, Max - Num_nonempty) ), gleam@list:fold([1, Extra, Min], 0, fun gleam@int:max/2), Num_required = gleam@int:max(Min - Num_nonempty, 0), Num_base = case {Num_nonempty, Num_required} of {X@1, Y} when (X@1 > 0) orelse (Y > 0) -> 0; {_, _} -> 1 end, Num_extra = gleam@int:min(Extra, Max - Num_nonempty), Num_optional = gleam@int:max(Num_base, Num_extra) - Num_required, {ok, lists:append( gleam@list:repeat( {unvalidated, <<""/utf8>>, required}, Num_required ), gleam@list:repeat( {unvalidated, <<""/utf8>>, optional}, Num_optional ) )} end end. -file("src/formz.gleam", 498). ?DOC( " Convenience function for creating a `LimitCheck` with a minimum number\n" " of required values. This sets the maximum to `1,000,000`, effectively unlimited.\n" ). -spec limit_at_least(integer()) -> fun((integer()) -> {ok, list(input_state())} | {error, integer()}). limit_at_least(Min) -> simple_limit_check(Min, 1000000, 1). -file("src/formz.gleam", 504). ?DOC( " Convenience function for creating a `LimitCheck` with a maximum number\n" " of accepted values. This sets the minimum to `0`.\n" ). -spec limit_at_most(integer()) -> fun((integer()) -> {ok, list(input_state())} | {error, integer()}). limit_at_most(Max) -> simple_limit_check(0, Max, 1). -file("src/formz.gleam", 510). ?DOC( " Convenience function for creating a `LimitCheck` with a minimum and maximum\n" " number of values.\n" ). -spec limit_between(integer(), integer()) -> fun((integer()) -> {ok, list(input_state())} | {error, integer()}). limit_between(Min, Max) -> simple_limit_check(Min, Max, 1). -file("src/formz.gleam", 628). -spec outputs_for_required_or_nonempty(list(list_parsing_result(GAM))) -> list({ok, GAM} | {error, binary()}). outputs_for_required_or_nonempty(States) -> gleam@list:filter_map( States, fun(Result) -> case {erlang:element(2, Result), erlang:element(3, Result)} of {<<""/utf8>>, optional} -> {error, nil}; {_, _} -> {ok, erlang:element(4, Result)} end end ). -file("src/formz.gleam", 807). -spec do_data(list(item(GCG)), gleam@dict:dict(binary(), list(binary()))) -> list(item(GCG)). do_data(Items, Data) -> gleam@list:map( Items, fun(Item) -> Values = gleam@dict:get( Data, erlang:element(2, erlang:element(2, Item)) ), case {Item, Values} of {{field, Detail, State, Widget}, {ok, [_ | _] = Values@1}} -> _assert_subject = gleam@list:last(Values@1), {ok, Last} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"formz"/utf8>>, function => <<"do_data"/utf8>>, line => 815}) end, {field, Detail, {unvalidated, Last, erlang:element(3, State)}, Widget}; {{list_field, Detail@1, States, Limit_check, Widget@1}, {ok, Values@2}} -> Nonempty = gleam@list:filter( Values@2, fun(V) -> not gleam@string:is_empty(V) end ), Num_nonempty = erlang:length(Nonempty), Additional_fields = begin _pipe = Limit_check(Num_nonempty), gleam@result:unwrap(_pipe, []) end, Items@1 = begin _pipe@1 = gleam@list:map2( States, Nonempty, fun(S, V@1) -> {unvalidated, V@1, erlang:element(3, S)} end ), _pipe@4 = lists:append( _pipe@1, begin _pipe@2 = Nonempty, _pipe@3 = gleam@list:drop( _pipe@2, erlang:length(States) ), gleam@list:map( _pipe@3, fun(V@2) -> {unvalidated, V@2, optional} end ) end ), lists:append(_pipe@4, Additional_fields) end, {list_field, Detail@1, Items@1, Limit_check, Widget@1}; {{sub_form, Detail@2, Items@2}, _} -> {sub_form, Detail@2, do_data(Items@2, Data)}; {_, _} -> Item end end ). -file("src/formz.gleam", 840). -spec to_dict(list({binary(), binary()})) -> gleam@dict:dict(binary(), list(binary())). to_dict(Values) -> gleam@list:fold_right( Values, gleam@dict:new(), fun(Acc, Kv) -> {Key, Value} = Kv, gleam@dict:upsert(Acc, Key, fun(Opt) -> case Opt of none -> [Value]; {some, Values@1} -> [Value | Values@1] end end) end ). -file("src/formz.gleam", 798). ?DOC( " Add input data to this form. This will set the raw string value of the fields.\n" " It does not trigger any parsing or decoding, so you can also use this to set\n" " default values (if you do it in your form generator function) or initial values\n" " (if you do it before rendering a blank form).\n" "\n" " The input data is a list of tuples, where the first element is the name of the\n" " field and the second element is the value to set. If the field does not exist\n" " the data is ignored.\n" "\n" " This resets the validation state of the fields that have data, so you'll need to\n" " re-validate or decode the form after setting data.\n" ). -spec data(form(GBZ, GCA), list({binary(), binary()})) -> form(GBZ, GCA). data(Form, Input_data) -> Data = to_dict(Input_data), {form, Items, Decode, Stub} = Form, {form, do_data(Items, Data), Decode, Stub}. -file("src/formz.gleam", 906). -spec mark_all_fields_as_valid(list(item(GDP))) -> list(item(GDP)). mark_all_fields_as_valid(Items) -> gleam@list:map(Items, fun(Item) -> case Item of {field, Field, State, Widget} -> {field, Field, {valid, erlang:element(2, State), erlang:element(3, State)}, Widget}; {list_field, Field@1, States, Limit_check, Widget@1} -> New_states = begin _pipe = States, gleam@list:map( _pipe, fun(State@1) -> {valid, erlang:element(2, State@1), erlang:element(3, State@1)} end ) end, {list_field, Field@1, New_states, Limit_check, Widget@1}; {sub_form, Subform, Items@1} -> {sub_form, Subform, mark_all_fields_as_valid(Items@1)} end end). -file("src/formz.gleam", 387). -spec add_field( config(), requirement(), FZM, fun((binary()) -> {ok, FZN} | {error, binary()}), FZN, fun((FZN) -> form(FZM, FZQ)) ) -> form(FZM, FZQ). add_field(Config, Requirement, Widget, Parse_field, Stub, Next) -> Next_form = Next(Stub), Updated_items = [{field, Config, {unvalidated, <<""/utf8>>, Requirement}, Widget} | erlang:element(2, Next_form)], Decode = fun(Items) -> [{field, Field, State, Widget@1} | Next_items] = case Items of [{field, _, _, _} | _] -> Items; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"formz"/utf8>>, function => <<"add_field"/utf8>>, line => 411}) end, Item_output = Parse_field(erlang:element(2, State)), Next_form@1 = Next( begin _pipe = Item_output, gleam@result:unwrap(_pipe, Stub) end ), Form_output = (erlang:element(3, Next_form@1))(Next_items), case {Form_output, Item_output} of {{ok, _}, {ok, _}} -> Form_output; {{error, Error_items}, {ok, _}} -> F = {field, Field, {valid, erlang:element(2, State), Requirement}, Widget@1}, {error, [F | Error_items]}; {{ok, _}, {error, Error}} -> F@1 = {field, Field, {invalid, erlang:element(2, State), Requirement, Error}, Widget@1}, {error, [F@1 | mark_all_fields_as_valid(Next_items)]}; {{error, Error_items@1}, {error, Error@1}} -> F@2 = {field, Field, {invalid, erlang:element(2, State), Requirement, Error@1}, Widget@1}, {error, [F@2 | Error_items@1]} end end, {form, Updated_items, Decode, erlang:element(4, Next_form)}. -file("src/formz.gleam", 342). ?DOC( " Add an optional field to a form.\n" "\n" " Ultimately whether a field is actually optional or not comes down to the\n" " details of the definition. The definition will receive the raw input string\n" " and is in charge of returning an error or an optional value.\n" "\n" " If multiple values are submitted for this field, the last one will be used.\n" "\n" " The final argument is a callback that will be called when the form\n" " is being... constructed to look for more fields; validated to check for\n" " errors; and decoded to parse the input data. **For this reason, the\n" " callback should be a function without side effects.** It can be called any\n" " number of times. Don't do anything but create the type with the data you\n" " need! If you need to do decoding that has side effects, you should use\n" " `decode_then_try`.\n" ). -spec field(config(), definition(FYQ, any(), FYS), fun((FYS) -> form(FYQ, FYW))) -> form(FYQ, FYW). field(Config, Definition, Next) -> add_field( Config, optional, erlang:element(2, Definition), fun(_capture) -> (erlang:element(5, Definition))( erlang:element(3, Definition), _capture ) end, erlang:element(6, Definition), Next ). -file("src/formz.gleam", 372). ?DOC( " Add a required field to a form.\n" "\n" " Ultimately whether a field is actually required or not comes down to the\n" " details of the definition. The definition will receive the raw input string\n" " and is in charge of returning an error or a value.\n" "\n" " If multiple values are submitted for this field, the last one will be used.\n" "\n" " The final argument is a callback that will be called when the form\n" " is being... constructed to look for more fields; validated to check for\n" " errors; and decoded to parse the input data. **For this reason, the\n" " callback should be a function without side effects.** It can be called any\n" " number of times. Don't do anything but create the type with the data you\n" " need! If you need to do decoding that has side effects, you should use\n" " `decode_then_try`.\n" ). -spec required_field( config(), definition(FZB, FZC, any()), fun((FZC) -> form(FZB, FZH)) ) -> form(FZB, FZH). required_field(Config, Definition, Next) -> add_field( Config, required, erlang:element(2, Definition), erlang:element(3, Definition), erlang:element(4, Definition), Next ). -file("src/formz.gleam", 737). ?DOC( " Add a form as a subform. This will essentially append the fields from the\n" " subform to the current form, prefixing their names with the name of the\n" " subform. Form generators will still see the fields as a set though, so they\n" " can be marked up as a group for accessibility reasons.\n" "\n" " The final argument is a callback that will be called when the form\n" " is being... constructed to look for more fields; validated to check for\n" " errors; and decoded to parse the input data. **For this reason, the\n" " callback should be a function without side effects.** It can be called any\n" " number of times. Don't do anything but create the type with the data you\n" " need! If you need to do decoding that has side effects, you should use\n" " `decode_then_try`.\n" ). -spec subform(config(), form(GBQ, GBR), fun((GBR) -> form(GBQ, GBU))) -> form(GBQ, GBU). subform(Config, Form, Next) -> Next_form = Next(erlang:element(4, Form)), Sub_items = begin _pipe = erlang:element(2, Form), gleam@list:map( _pipe, fun(_capture) -> add_prefix_to_item(_capture, erlang:element(2, Config)) end ) end, Subform = {sub_form, Config, Sub_items}, Updated_items = [Subform | erlang:element(2, Next_form)], Decode = fun(Items) -> [{sub_form, Details, Sub_items@1} | Next_items] = case Items of [{sub_form, _, _} | _] -> Items; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"formz"/utf8>>, function => <<"subform"/utf8>>, line => 750}) end, Item_output = (erlang:element(3, Form))(Sub_items@1), Next_form@1 = Next( begin _pipe@1 = Item_output, gleam@result:unwrap(_pipe@1, erlang:element(4, Form)) end ), Form_output = (erlang:element(3, Next_form@1))(Next_items), case {Form_output, Item_output} of {{ok, _}, {ok, _}} -> Form_output; {{error, Next_error_items}, {ok, _}} -> Sub = {sub_form, Details, begin _pipe@2 = Sub_items@1, mark_all_fields_as_valid(_pipe@2) end}, {error, [Sub | Next_error_items]}; {{ok, _}, {error, Error_items}} -> Sub@1 = {sub_form, Details, Error_items}, {error, [Sub@1 | mark_all_fields_as_valid(Next_items)]}; {{error, Next_error_items@1}, {error, Error_items@1}} -> Sub@2 = {sub_form, Details, Error_items@1}, {error, [Sub@2 | Next_error_items@1]} end end, {form, Updated_items, Decode, erlang:element(4, Next_form)}. -file("src/formz.gleam", 894). ?DOC( " Decode the form, then apply a function to the output if it was successful.\n" " This is a very thin wrapper around `decode` and `result.try`, but the\n" " difference being it will pass the form along to the function as a second\n" " argument in addition to the successful result. This allows you to easily\n" " update the form fields with errors or other information based on the output.\n" "\n" " This is useful for situations where you can have errors in the form that\n" " aren't easily checked in simple parsing functions. Like, say, hitting a\n" " db to check if a username is taken.\n" "\n" " As a reminder, parse functions will be called multiple times for each field\n" " when the form is being made, validated and parsed, and should not contain\n" " side effects. This function is the proper way to add errors to fields from\n" " functions that have side effects.\n" "\n" " ```gleam\n" " make_form()\n" " |> data(form_data)\n" " |> decode_then_try(fn(username, form) {\n" " case is_username_taken(username) {\n" " Ok(false) -> Ok(form)\n" " Ok(true) -> field_error(form, \"username\", \"Username is taken\")\n" " }\n" " }\n" ). -spec decode_then_try( form(GDA, GDB), fun((form(GDA, GDB), GDB) -> {ok, GDG} | {error, form(GDA, GDB)}) ) -> {ok, GDG} | {error, form(GDA, GDB)}. decode_then_try(Form, Fun) -> _pipe = Form, _pipe@1 = decode(_pipe), gleam@result:'try'( _pipe@1, fun(Output) -> Items = begin _pipe@2 = erlang:element(2, Form), mark_all_fields_as_valid(_pipe@2) end, Fun( begin _record = Form, {form, Items, erlang:element(3, _record), erlang:element(4, _record)} end, Output ) end ). -file("src/formz.gleam", 986). ?DOC( " Get the [`Item`](https://hexdocs.pm/formz/formz.html#Item) with the\n" " given name. If multiple items have the same name, the first one is returned.\n" ). -spec get(form(GEN, any()), binary()) -> {ok, item(GEN)} | {error, nil}. get(Form, Name) -> gleam@list:find( erlang:element(2, Form), fun(Item) -> Name =:= get_item_name(Item) end ). -file("src/formz.gleam", 928). ?DOC( " Validate specific fields of the form. This is similar to `decode`, but\n" " instead of returning the decoded output if there are no errors, it returns\n" " the valid form. This is useful for if you want to be able to give feedback\n" " to the user about whether certain fields are valid or not. For example, you\n" " could just validate only fields that the user has interacted with.\n" ). -spec validate(form(GDU, GDV), list(binary())) -> form(GDU, GDV). validate(Form, Names) -> case (erlang:element(3, Form))(erlang:element(2, Form)) of {ok, _} -> Items = gleam@list:map( erlang:element(2, Form), fun(Parsed_item) -> Item_name = get_item_name(Parsed_item), case gleam@list:find( Names, fun(Name) -> Item_name =:= Name end ) of {ok, _} -> _assert_subject = begin _pipe = [Parsed_item], _pipe@1 = mark_all_fields_as_valid(_pipe), gleam@list:first(_pipe@1) end, {ok, Item} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"formz"/utf8>>, function => <<"validate"/utf8>>, line => 939}) end, Item; {error, _} -> _assert_subject@1 = get(Form, Item_name), {ok, Original_item} = case _assert_subject@1 of {ok, _} -> _assert_subject@1; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail@1, module => <<"formz"/utf8>>, function => <<"validate"/utf8>>, line => 944}) end, Original_item end end ), _record = Form, {form, Items, erlang:element(3, _record), erlang:element(4, _record)}; {error, Items@1} -> Items@2 = gleam@list:map( Items@1, fun(Parsed_item@1) -> Item_name@1 = get_item_name(Parsed_item@1), case gleam@list:find( Names, fun(Name@1) -> Item_name@1 =:= Name@1 end ) of {ok, _} -> Parsed_item@1; {error, _} -> _assert_subject@2 = get(Form, Item_name@1), {ok, Original_item@1} = case _assert_subject@2 of {ok, _} -> _assert_subject@2; _assert_fail@2 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail@2, module => <<"formz"/utf8>>, function => <<"validate"/utf8>>, line => 958}) end, Original_item@1 end end ), _record@1 = Form, {form, Items@2, erlang:element(3, _record@1), erlang:element(4, _record@1)} end. -file("src/formz.gleam", 972). ?DOC( " Validate all the fields in the form. This is similar to `decode`, but\n" " instead of returning the decoded output if there are no errors, it returns\n" " the valid form. This is useful for if you want to be able to give feedback\n" " to the user about whether certain fields are valid or not.\n" ). -spec validate_all(form(GEB, GEC)) -> form(GEB, GEC). validate_all(Form) -> _pipe = erlang:element(2, Form), _pipe@1 = gleam@list:map(_pipe, fun get_item_name/1), validate(Form, _pipe@1). -file("src/formz.gleam", 1005). -spec do_update(list(item(GFB)), binary(), fun((item(GFB)) -> item(GFB))) -> list(item(GFB)). do_update(Items, Name, Fun) -> gleam@list:map(Items, fun(Item) -> case Item of {field, Detail, _, _} when erlang:element(2, Detail) =:= Name -> Fun(Item); {list_field, Detail@1, _, _, _} when erlang:element(2, Detail@1) =:= Name -> Fun(Item); {sub_form, Detail@2, Items@1} -> Sub = {sub_form, Detail@2, do_update(Items@1, Name, Fun)}, case erlang:element(2, Detail@2) =:= Name of true -> Fun(Sub); false -> Sub end; _ -> Item end end). -file("src/formz.gleam", 996). ?DOC( " Update the [`Item`](https://hexdocs.pm/formz/formz.html#Item) with\n" " the given name using the provided function. If multiple items have the same\n" " name, it will be called on all of them.\n" ). -spec update(form(GEU, GEV), binary(), fun((item(GEU)) -> item(GEU))) -> form(GEU, GEV). update(Form, Name, Fun) -> Items = do_update(erlang:element(2, Form), Name, Fun), _record = Form, {form, Items, erlang:element(3, _record), erlang:element(4, _record)}. -file("src/formz.gleam", 1036). ?DOC( " Update the `Field` [details](https://hexdocs.pm/formz/formz/field.html) with\n" " the given name using the provided function. If multiple items have the same\n" " name, it will be called on all of them. If no items have the given name,\n" " or an item with the given name exists but isn't a `Field`, this function\n" " will do nothing.\n" "\n" " ```gleam\n" " let form = make_form()\n" " update_field(form, \"name\", field.set_label(_, \"Full Name\"))\n" " ```\n" ). -spec update_config(form(GFI, GFJ), binary(), fun((config()) -> config())) -> form(GFI, GFJ). update_config(Form, Name, Fun) -> update(Form, Name, fun(Item) -> case Item of {field, Details, _, _} -> _record = Item, {field, Fun(Details), erlang:element(3, _record), erlang:element(4, _record)}; {list_field, Details@1, _, _, _} -> _record@1 = Item, {list_field, Fun(Details@1), erlang:element(3, _record@1), erlang:element(4, _record@1), erlang:element(5, _record@1)}; {sub_form, Details@2, _} -> _record@2 = Item, {sub_form, Fun(Details@2), erlang:element(3, _record@2)} end end). -file("src/formz.gleam", 1058). ?DOC( " Convenience function for setting the `InputState` of a field to\n" " Invalid with a given error message.\n" "\n" " ### Example\n" "\n" " ```\n" " field_error(form, \"username\", \"Username is taken\")\n" " ```\n" ). -spec field_error(form(GFO, GFP), binary(), binary()) -> form(GFO, GFP). field_error(Form, Name, Str) -> update(Form, Name, fun(Item) -> case Item of {field, Field, State, Widget} -> {field, Field, {invalid, erlang:element(2, State), erlang:element(3, State), Str}, Widget}; _ -> Item end end). -file("src/formz.gleam", 1085). ?DOC( " Convenience function for setting the `InputState`s of a list field. This\n" " takes a list of Results, where the Ok means the input is `Valid` and\n" " `Error` means the input is `Invalid` with the given error message.\n" "\n" " This does not clear any existing errors, it will just set the errors marked\n" " in the input list. If you want to clear errors you'll have to use the\n" " `update` function and do it manually.\n" "\n" " ### Example\n" "\n" " ```\n" " listfield_errors(form, \"pet_names\", [Ok(Nil), Ok(Nil), Error(\"Must be a cat\")])\n" " ```\n" ). -spec listfield_errors( form(GFU, GFV), binary(), list({ok, nil} | {error, binary()}) ) -> form(GFU, GFV). listfield_errors(Form, Name, Errors) -> update(Form, Name, fun(Item) -> case Item of {list_field, Field, States, Limit_check, Widget} -> Invalid_states = gleam@list:map2( States, Errors, fun(State, Err) -> case Err of {error, Str} -> {invalid, erlang:element(2, State), erlang:element(3, State), Str}; {ok, nil} -> State end end ), {list_field, Field, Invalid_states, Limit_check, Widget}; _ -> Item end end). -file("src/formz.gleam", 1248). -spec do_get_states(list(item(any()))) -> list(input_state()). do_get_states(Items) -> gleam@list:fold(Items, [], fun(Acc, Item) -> case Item of {field, _, State, _} -> [State | Acc]; {list_field, _, States, _, _} -> lists:append( begin _pipe = States, lists:reverse(_pipe) end, Acc ); {sub_form, _, Sub_items} -> gleam@list:flatten([do_get_states(Sub_items), Acc]) end end). -file("src/formz.gleam", 1244). ?DOC(false). -spec get_states(form(any(), any())) -> list(input_state()). get_states(Form) -> _pipe = erlang:element(2, Form), _pipe@1 = do_get_states(_pipe), lists:reverse(_pipe@1).