{"id":16102380,"url":"https://github.com/sshaw/mojolicious-plugin-formfields","last_synced_at":"2025-07-23T08:34:57.116Z","repository":{"id":2881893,"uuid":"3888402","full_name":"sshaw/Mojolicious-Plugin-FormFields","owner":"sshaw","description":"Use objects and data structures in your forms","archived":false,"fork":false,"pushed_at":"2019-11-10T00:28:28.000Z","size":63,"stargazers_count":3,"open_issues_count":1,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-02T13:41:40.555Z","etag":null,"topics":["form","form-builder","form-validation","mojolicious","perl"],"latest_commit_sha":null,"homepage":"","language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sshaw.png","metadata":{"files":{"readme":"README.pod","changelog":"Changes","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-03-31T21:13:39.000Z","updated_at":"2019-11-10T00:28:30.000Z","dependencies_parsed_at":"2022-07-31T14:09:29.788Z","dependency_job_id":null,"html_url":"https://github.com/sshaw/Mojolicious-Plugin-FormFields","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/sshaw/Mojolicious-Plugin-FormFields","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshaw%2FMojolicious-Plugin-FormFields","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshaw%2FMojolicious-Plugin-FormFields/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshaw%2FMojolicious-Plugin-FormFields/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshaw%2FMojolicious-Plugin-FormFields/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sshaw","download_url":"https://codeload.github.com/sshaw/Mojolicious-Plugin-FormFields/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshaw%2FMojolicious-Plugin-FormFields/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266645470,"owners_count":23961734,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-23T02:00:09.312Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["form","form-builder","form-validation","mojolicious","perl"],"created_at":"2024-10-09T18:53:41.841Z","updated_at":"2025-07-23T08:34:57.096Z","avatar_url":"https://github.com/sshaw.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"=pod\n\n=head1 NAME\n\nMojolicious::Plugin::FormFields - Lightweight form builder with validation and filtering\n\n=head1 SYNOPSIS\n\n  $self-\u003eplugin('FormFields');\n\n  # In your controller\n  sub edit\n  {\n      my $self = shift;\n      my $user = $self-\u003eusers-\u003efind($self-\u003eparam('id'));\n      $self-\u003estash(user =\u003e $user);\n  }\n\n  sub update\n  {\n      my $self = shift;\n      my $user = $self-\u003eparams('user');\n\n      $self-\u003efield('user.name')-\u003eis_required;\n      $self-\u003efield('user.password')-\u003eis_required-\u003eis_equal('user.confirm_password');\n\n      if($self-\u003evalid) {\n\t  $self-\u003eusers-\u003esave($user);\n\t  $self-\u003eredirect_to('/profile');\n\t  return;\n      }\n\n      $self-\u003estash(user =\u003e $user);\n  }\n\n  # In your view\n  field('user.name')-\u003etext\n  field('user.name')-\u003eerror unless field('user.name')-\u003evalid\n\n  field('user.password')-\u003epassword\n  field('user.age')-\u003eselect([10,20,30])\n  field('user.password')-\u003epassword\n  field('user.taste')-\u003eradio('me_gusta')\n  field('user.taste')-\u003eradio('estoy_harto_de')\n  field('user.orders.0.id')-\u003ehidden\n\n  # Fields for a collection\n  my $kinfolk = field('user.kinfolk');\n  for my $person (@$kinfolk) {\n    $person-\u003ehidden('id')\n    $person-\u003etext('name')\n  }\n\n  # Or, scope it to the 'user' param\n  my $user = fields('user')\n  $user-\u003ehidden('id')\n  $user-\u003etext('name')\n  $user-\u003eerror('name') unless $user-\u003evalid('name')\n  $user-\u003elabel('admin')\n  $user-\u003echeckbox('admin')\n  $user-\u003epassword('password')\n  $user-\u003eselect('age', [ [X =\u003e 10], [Dub =\u003e 20] ])\n  $user-\u003efile('avatar')\n  $user-\u003etextarea('bio', size =\u003e '10x50')\n\n  my $kinfolk = $user-\u003efields('kinfolk')\n  for my $person (@$kinfolk) {\n    $person-\u003etext('name')\n    # ...\n  }\n\n=head1 DESCRIPTION\n\nC\u003cMojolicious::Plugin::FormFields\u003e allows you to bind objects and data structures to form fields. It also performs validation and filtering via L\u003cValidate::Tiny\u003e.\n\n=head1 CREATING FIELDS\n\nFields can be bound to a hash, an array, something blessed, or any combination of the three.\nThey are created by calling the C\u003c\u003c L\u003c/field\u003e \u003e\u003e helper with a path to the value you want to bind,\nand then calling the desired HTML input method\n\n  field('user.name')-\u003etext\n\nIs the same as\n\n  text_field 'user.name', $user-\u003ename, id =\u003e 'user-name'\n\n(though C\u003cMojolicious::Plugin::FormFields\u003e sets C\u003ctype=\"text\"\u003e).\n\nField names/paths are given in the form C\u003ctarget.accessor1 [ .accessor2 [ .accessorN ] ]\u003e where C\u003ctarget\u003e is an object or\ndata structure and C\u003caccessor\u003e is a method, hash key, or array index. The target must be in the stash under the key C\u003ctarget\u003e\nor provided as an argument to C\u003c\u003c L\u003c/field\u003e \u003e\u003e.\n\nSome examples:\n\n  field('users.0.name')-\u003etext\n\nIs the same as\n\n  text_field 'users.0.name', $users-\u003e[0]-\u003ename, id =\u003e 'users-0-name'\n\nAnd\n\n  field('item.orders.0.XAJ123.quantity')-\u003etext\n\nIs equivalent to\n\n  text_field 'item.orders.0.XAJ123.quantity', $item-\u003eorders-\u003e[0]-\u003e{XAJ123}-\u003equantity, id =\u003e 'item-orders-0-XAJ123-quantity'\n\nAs you can see DOM IDs are always created.\n\nHere the target key C\u003cbook\u003e does not exist in the stash so the target is supplied\n\n  field('book.upc', $item)-\u003etext\n\nIf a value for the flattened representation exists (e.g., from a form submission) it will be used instead of\nthe value pointed at by the field name (desired behavior?). This is the same as Mojolicious' Tag Helpers.\n\nOptions can also be provided\n\n  field('user.name')-\u003etext(class =\u003e 'input-text', data =\u003e { name =\u003e 'xxx' })\n\nSee L\u003c/SUPPORTED FIELDS\u003e for the list of HTML input creation methods.\n\n=head2 STRUCTURED REQUEST PARAMETERS\n\nStructured request parameters for the bound object/data structure are available via\nC\u003cMojolicious::Controller\u003e's L\u003cparam method|Mojolicious::Controller#param\u003e.\nThey can not be accessed via C\u003cMojo::Message::Request\u003e.\n\nA request with the parameters C\u003cuser.name=nameA\u0026user.email=email\u0026id=123\u003e can be accessed in your action like\n\n  my $user = $self-\u003eparam('user');\n  $user-\u003e{name};\n  $user-\u003e{email};\n\nOther parameters can be accessed as usual\n\n  $id = $self-\u003eparam('id');\n\nThe flattened parameter can also be used\n\n  $name = $self-\u003eparam('user.name');\n\nSee L\u003cMojolicious::Plugin::ParamExpand\u003e for more info.\n\n=head2 SCOPING\n\nFields can be scoped to a particular object/data structure via the C\u003c\u003c L\u003c/fields\u003e \u003e\u003e helper\n\n  my $user = fields('user');\n  $user-\u003etext('name');\n  $user-\u003ehidden('id');\n\nWhen using C\u003cfields\u003e you must supply the field's name to the HTML input and validation methods, otherwise\nthe calls are the same as they are with C\u003cfield\u003e.\n\n=head2 COLLECTIONS\n\nYou can also create fields scoped to elements in a collection\n\n  my $addresses = field('user.addresses');\n  for my $addr (@$addresses) {\n    # field('user.addresses.N.id')-\u003ehidden\n    $addr-\u003ehidden('id');\n\n    # field('user.addresses.N.street')-\u003etext\n    $addr-\u003etext('street');\n\n    # field('user.addresses.N.city')-\u003eselect([qw|OAK PHL LAX|])\n    $addr-\u003eselect('city', [qw|OAK PHL LAX|]);\n  }\n\nOr, for fields that are already scoped\n\n  my $user = fields('user')\n  $user-\u003ehidden('id');\n\n  my $addressess = $user-\u003efields('addresses');\n  for my $addr (@$addresses) {\n    $addr-\u003ehidden('id')\n    # ...\n  }\n\nYou can also access the underlying object and its position within a collection\nvia the C\u003cobject\u003e and C\u003cindex\u003e methods.\n\n  \u003c% for my $addr (@$addresses) {  %\u003e\n    \u003cdiv id=\"\u003c%= dom_id($addr-\u003eobject) %\u003e\"\u003e\n      \u003ch3\u003eAddress #\u003c%= $addr-\u003eindex + 1 %\u003e\u003c/h3\u003e\n      \u003c%= $addr-\u003ehidden('id') %\u003e\n      ...\n    \u003c/div\u003e\n  \u003c% } %\u003e\n\n=head1 VALIDATING \u0026 FILTERING\n\nValidation rules are created by calling validation and/or filter methods\non the field to be validated\n\n  # In your controller\n  my $self = shift;\n  $self-\u003efield('user.name')-\u003eis_required;\n  $self-\u003efield('user.name')-\u003efilter('trim');\n\nThese methods can be chained\n\n  $self-\u003efield('user.name')-\u003eis_required-\u003efilter('trim');\n\nTo perform validation on a field call its C\u003cvalid\u003e method\n\n  $field = $self-\u003efield('user.name');\n  $field-\u003eis_required;\n  $field-\u003evalid;\n  $field-\u003eerror;\n\nThis will only validate and return the error for the C\u003cuser.name\u003e field. To validate all fields and retrieve all error messages call the controller's C\u003cvalid\u003e and C\u003cerrors\u003e methods\n\n  $self-\u003efield('user.name')-\u003eis_required;\n  $self-\u003efield('user.age')-\u003eis_like(qr/^\\d+$/);\n  $self-\u003evalid;\n\n  my $errors = $self-\u003eerrors;\n  $errors-\u003e{'user.name'}\n  # ...\n\nOf course the C\u003cerror\u003e/C\u003cerrors\u003e and C\u003cvalid\u003e methods can be used in your view too\n\n  \u003c% unless(valid()) { %\u003e\n    \u003cp\u003eHey, fix the below errors\u003c/p\u003e\n  \u003c% } %\u003e\n\n  \u003c%= field('name')-\u003etext %\u003e\n  \u003c% unless(field('name')-\u003evalid) { %\u003e\n    \u003cspan class=\"error\"\u003e\u003c%= field('name')-\u003eerror %\u003e\u003c/span\u003e\n  \u003c% } %\u003e\n\nWhen creating validation rules for L\u003c/fields\u003e you must pass the field name as the first argument\n\n  my $user = fields('user');\n  $user-\u003eis_required('password');\n  $user-\u003eis_equal(password =\u003e 'confirm_password');\n  $user-\u003eis_long_at_least(password =\u003e 8, 'Mais longo caipira');\n\n=head2 AVAILABLE RULES \u0026 FILTERS\n\nC\u003cMojolicious::Plugin::FormFields\u003e uses C\u003cValidate::Tiny\u003e, see L\u003cits docs|Validate::Tiny/filter\u003e for a list.\n\n=head2 RENAMING THE VALIDATION METHODS\n\nIn the event that the C\u003cvalid\u003e and/or C\u003cerrors\u003e methods clash with exiting methods/helpers\nin your app you can rename them by specifying alternate names when loading the plugin\n\n  $self-\u003eplugin('FormFields', methods =\u003e { valid =\u003e 'form_valid', errors =\u003e 'form_errors' });\n  # ...\n\n  $self-\u003efield('user.name')-\u003eis_required;\n  $self-\u003eform_valid;\n  $self-\u003eform_errors;\n\nNote that this I\u003conly\u003e changes the methods B\u003con the controller\u003e and does not change the methods on the object returned by C\u003cfield\u003e.\n\n=head1 METHODS\n\n=head2 field\n\n  field($name)-\u003etext\n  field($name, $object)-\u003etext\n\n=head3 Arguments\n\nC\u003c$name\u003e\n\nThe field's name, which can also be the path to its value in the stash. See L\u003c/CREATING FIELDS\u003e.\n\nC\u003c$object\u003e\n\nOptional. The object used to retrieve the value specified by C\u003c$name\u003e. Must be a reference to a\nhash, an array, or something blessed. If not given the value will be retrieved from\nthe stash or, for previously submitted forms, the request parameter C\u003c$name\u003e.\n\n=head3 Returns\n\nAn object than can be used to create HTML form fields, see L\u003c/SUPPORTED FIELDS\u003e.\n\n=head3 Errors\n\nAn error will be raised if:\n\n=over 4\n\n=item * C\u003c$name\u003e is not provided\n\n=item * C\u003c$name\u003e cannot be retrieved from C\u003c$object\u003e\n\n=item * C\u003c$object\u003e cannot be found in the stash and no default was given\n\n=back\n\n=head3 Collections\n\nSee L\u003c/COLLECTIONS\u003e\n\n=head2 fields\n\n  $f = fields($name)\n  $f-\u003etext('address')\n\n  $f = fields($name, $object)\n  $f-\u003etext('address')\n\nCreate form fields scoped to a parameter.\n\nFor example\n\n  % $f = fields('user')\n  %= $f-\u003eselect('age', [10,20,30])\n  %= $f-\u003etextarea('bio')\n\nIs the same as\n\n  %= field('user.age')-\u003eselect([10,20,30])\n  %= field('user.bio')-\u003etextarea\n\n=head3 Arguments\n\nSame as L\u003c/field\u003e.\n\n=head3 Returns\n\nAn object than can be used to create HTML form fields scoped to the C\u003c$name\u003e argument, see L\u003c/SUPPORTED FIELDS\u003e.\n\n=head3 Errors\n\nSame as L\u003c/field\u003e.\n\n=head3 Collections\n\nSee L\u003c/COLLECTIONS\u003e\n\n=head1 SUPPORTED FIELDS\n\n=head2 checkbox\n\n  field('user.admin')-\u003echeckbox(%options)\n  field('user.admin')-\u003echeckbox('yes', %options)\n\nCreates\n\n  \u003cinput type=\"checkbox\" name=\"user.admin\" id=\"user-admin-1\" value=\"1\"/\u003e\n  \u003cinput type=\"checkbox\" name=\"user.admin\" id=\"user-admin-yes\" value=\"yes\"/\u003e\n\n=head2 file\n\n  field('user.avatar')-\u003efile(%options);\n\nCreates\n\n  \u003cinput id=\"user-avatar\" name=\"user.avatar\" type=\"file\" /\u003e\n\n=head2 hidden\n\n  field('user.id')-\u003ehidden(%options)\n\nCreates\n\n  \u003cinput id=\"user-id\" name=\"user.id\" type=\"hidden\" value=\"123123\" /\u003e\n\n=head2 input\n\n  field('user.phone')-\u003einput($type, %options)\n\nFor example\n\n  field('user.phone')-\u003einput('tel', pattern =\u003e '\\d{3}-\\d{4}')\n\nCreates\n\n  \u003cinput id=\"user-phone\" name=\"user.phone\" type=\"tel\" pattern=\"\\d{3}-\\d{4}\" /\u003e\n\n=head2 label\n\n  field('user.name')-\u003elabel\n  field('user.name')-\u003elabel('Nombre', for =\u003e \"tu_nombre_hyna\")\n\nCreates\n\n  \u003clabel for=\"user-name\"\u003eName\u003c/label\u003e\n  \u003clabel for=\"tu_nombre_hyna\"\u003eNombre\u003c/label\u003e\n\n=head2 password\n\n  field('user.password')-\u003epassword(%options)\n\nCreates\n\n  \u003cinput id=\"user-password\" name=\"user.password\" type=\"password\" /\u003e\n\n=head2 select\n\n  field('user.age')-\u003eselect([10,20,30], %options)\n  field('user.age')-\u003eselect([[Ten =\u003e 10], [Dub =\u003e 20], [Trenta =\u003e 30]], %options)\n\nCreates\n\n  \u003cselect id=\"user-age\" name=\"user.age\"\u003e\n    \u003coption value=\"10\"\u003e10\u003c/option\u003e\n    \u003coption value=\"20\"\u003e20\u003c/option\u003e\n    \u003coption value=\"30\"\u003e30\u003c/option\u003e\n  \u003c/select\u003e\n\n  \u003cselect id=\"user-age\" name=\"user.age\"\u003e\n    \u003coption value=\"10\"\u003eTen\u003c/option\u003e\n    \u003coption value=\"20\"\u003eDub\u003c/option\u003e\n    \u003coption value=\"30\"\u003eTrenta\u003c/option\u003e\n  \u003c/select\u003e\n\n=head2 radio\n\n  field('user.age')-\u003eradio('older_than_21', %options)\n\nCreates\n\n  \u003cinput id=\"user-age-older_than_21\" name=\"user.age\" type=\"radio\" value=\"older_than_21\" /\u003e\n\n=head2 text\n\n  field('user.name')-\u003etext(%options)\n  field('user.name')-\u003etext(size =\u003e 10, maxlength =\u003e 32)\n\nCreates\n\n  \u003cinput id=\"user-name\" name=\"user.name\" value=\"sshaw\" /\u003e\n  \u003cinput id=\"user-name\" name=\"user.name\" value=\"sshaw\" size=\"10\" maxlength=\"32\" /\u003e\n\n=head2 textarea\n\n  field('user.bio')-\u003etextarea(%options)\n  field('user.bio')-\u003etextarea(size =\u003e '10x50')\n\nCreates\n\n  \u003ctextarea id=\"user-bio\" name=\"user.bio\"\u003eProprietary and confidential\u003c/textarea\u003e\n  \u003ctextarea cols=\"50\" id=\"user-bio\" name=\"user.bio\" rows=\"10\"\u003eProprietary and confidential\u003c/textarea\u003e\n\n=head1 AUTHOR\n\nSkye Shaw (sshaw [AT] gmail.com)\n\n=head1 SEE ALSO\n\nL\u003cMojolicious::Plugin::TagHelpers\u003e, L\u003cMojolicious::Plugin::ParamExpand\u003e, L\u003cValidate::Tiny\u003e, L\u003cMojolicious::Plugin::DomIdHelper\u003e\n\n=head1 COPYRIGHT\n\nCopyright (c) 2012-2014 Skye Shaw.\n\nThis library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsshaw%2Fmojolicious-plugin-formfields","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsshaw%2Fmojolicious-plugin-formfields","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsshaw%2Fmojolicious-plugin-formfields/lists"}