Current section

Files

Jump to
formz src formz.erl
Raw

src/formz.erl

-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"
" <table>\n"
" <tr>\n"
" <td>Creating a form</td>\n"
" <td>\n"
" <a href=\"#create_form\">create_form</a><br>\n"
" <a href=\"#required_field\">required_field</a><br>\n"
" <a href=\"#field\">field</a><br>\n"
" <a href=\"#list\">list</a><br>\n"
" <a href=\"#limited_list\">limited_list</a><br>\n"
" <a href=\"#subform\">subform</a>\n"
" </td>\n"
" </tr>\n"
" <tr>\n"
" <td>Decoding and validating a form</td>\n"
" <td>\n"
" <a href=\"#data\">data</a><br>\n"
" <a href=\"#decode\">decode</a><br>\n"
" <a href=\"#decode_then_try\">decode_then_try</a><br>\n"
" <a href=\"#validate\">validate</a><br>\n"
" <a href=\"#validate_all\">validate_all</a>\n"
" </td>\n"
" </tr>\n"
" <tr>\n"
" <td>Creating a field definition</td>\n"
" <td>\n"
" <a href=\"#definition\">definition</a><br>\n"
" <a href=\"#definition_with_custom_optional\">definition_with_custom_optional</a><br>\n"
" <a href=\"#verify\">verify</a><br>\n"
" <a href=\"#widget\">widget</a>\n"
" </td>\n"
" </tr>\n"
" <tr>\n"
" <td>Creating a limited list</td>\n"
" <td>\n"
" <a href=\"#limit_at_least\">limit_at_least</a><br>\n"
" <a href=\"#limit_at_most\">limit_at_most</a><br>\n"
" <a href=\"#limit_between\">limit_between</a><br>\n"
" <a href=\"#simple_limit_check\">simple_limit_check</a>\n"
" </td>\n"
" </tr>\n"
" <tr>\n"
" <td>Accessing and manipulating form items</td>\n"
" <td>\n"
" <a href=\"#get\">get</a><br>\n"
" <a href=\"#items\">items</a><br>\n"
" <a href=\"#field_error\">field_error</a><br>\n"
" <a href=\"#listfield_errors\">listfield_errors</a><br>\n"
" <a href=\"#update\">update</a><br>\n"
" </td>\n"
" </tr>\n"
" <tr>\n"
" <td>Accessing and manipulating config for a form item</td>\n"
" <td>\n"
" <a href=\"#update_config\">update_config</a><br>\n"
" <a href=\"#set_name\">set_name</a><br>\n"
" <a href=\"#set_label\">set_label</a><br>\n"
" <a href=\"#set_help_text\">set_help_text</a><br>\n"
" <a href=\"#set_disabled\">set_disabled</a><br>\n"
" <a href=\"#make_disabled\">make_disabled</a><br>\n"
" </td>\n"
" </tr>\n"
" </table>\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 = <<<<Prefix/binary, "-"/utf8>>/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 = <<<<Prefix/binary, "-"/utf8>>/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 = <<<<Prefix/binary, "-"/utf8>>/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 `<div>`\n"
" disabled as group but you can do this with a `<fieldset>`.\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).