/lib/HTML/FormHandler/Manual/Cookbook.pod

http://github.com/gshank/html-formhandler · Unknown · 799 lines · 606 code · 193 blank · 0 comment · 0 complexity · 6fb25a8f409e5609811de4a2ff9fc697 MD5 · raw file

  1. package HTML::FormHandler::Manual::Cookbook;
  2. # ABSTRACT: FormHandler use recipes
  3. =head1 SYNOPSIS
  4. L<Manual Index|HTML::FormHandler::Manual>
  5. Collection of use recipes for L<HTML::FormHandler>
  6. =head2 No form file, no template file...
  7. I had to create a tiny little form this week for admins to enter a comment, and
  8. it seemed silly to have to create a form file and a template file. I remembered
  9. that you can set the TT 'template' to a string reference and not use a template
  10. at all, which is nice when FormHandler will create the form HTML for you anyway.
  11. sub comment : Chained('base_sub') PathPart('comment') Args(0) {
  12. my ( $self, $c ) = @_;
  13. my $form = HTML::FormHandler->new( field_list =>
  14. [ comment => { type => 'Text', size => 60 },
  15. submit => {type => 'Submit'} ] );
  16. $form->process($c->req->params);
  17. if ( $form->validated ) {
  18. $self->admin_log( $c, "Admin::Queue", "admin comment",
  19. $form->field('comment')->value );
  20. $c->flash( message => 'Comment added' );
  21. $c->res->redirect( $c->stash->{urilist}->{view} );
  22. }
  23. my $rendered_form = $form->render;
  24. $c->stash( template => \$rendered_form );
  25. }
  26. This creates the form on the fly with a comment field and a submit button,
  27. renders it using the default TT wrappers, then logs the comment. No other files
  28. at all....
  29. FormHandler isn't really necessary for validation here, but it does make it
  30. possible to have a simple, standalone method.
  31. =head2 Dynamically change the active fields
  32. A common use case is for forms with some fields that should be displayed in
  33. some circumstances and not in others. There are a number of ways to do this.
  34. One way is to use the 'field_list' method:
  35. sub field_list {
  36. my $self = shift;
  37. my @fields;
  38. <build list of fields>
  39. return \@fields;
  40. }
  41. This only happens at form construction time, however. Another method that
  42. works is to define all of the possible fields in your form, and mark some
  43. of them 'inactive';
  44. package MyApp::Variable::Form;
  45. use HTML::FormHandler::Moose;
  46. extends 'HTML::FormHandler';
  47. has_field 'foo';
  48. has_field 'bar' => ( inactive => 1 );
  49. 1;
  50. Set to 'active' or 'inactive' on the 'process' call:
  51. $form->process( params => $params, active => ['foo', 'bar'] );
  52. ...
  53. $form->process( params => $params, inactive => ['bar'] );
  54. If you need to check some other state to determine whether or not a field should
  55. be active, you can do that using a Moose method modifier on 'set_active':
  56. before 'set_active' => sub {
  57. my $self = shift;
  58. $self->active(['foo', bar']) if ( <some_condition> );
  59. };
  60. Fields set to active/inactive on the 'process' call are automatically set back
  61. to inactive when the form is cleared, so there's no need to reset.
  62. If you want the fields activated for the life of an object, set active on new:
  63. my $form = MyApp::Form::User->new( active => ['opt_in', 'active']);
  64. =head2 Add custom attributes to FormHandler fields
  65. If you want to add custom attributes to the FormHandler fields but don't want
  66. to subclass all the fields, you can apply a role containing the new
  67. attributes to an L<HTML::FormHandler::Field> in your form.
  68. Use 'traits' on the individual fields to apply a role to field instances.
  69. Use the form attribute 'field_traits' to apply a role to all field instances in
  70. the form.
  71. package MyApp::Form::Test;
  72. use HTML::FormHandler::Moose;
  73. extends 'HTML::FormHandler';
  74. has_field 'foo' => ( traits => ['MyApp::TraitFor::Test'] );
  75. has '+field_traits' => ( default => sub { ['Some::Trait', 'Another::Trait'] } );
  76. Or set the traits on new:
  77. my $form = MyApp::Form::User->new( field_traits => ['MyApp::TraitFor::Test'] );
  78. my $form = MyApp::Form::User->new(
  79. field_list => [ '+foo' => { traits => [...] } ]);
  80. To apply the role to a field base class, use 'apply_traits' on that class:
  81. HTML::FormHandler::Field->apply_traits( 'Some::Test' );
  82. HTML::FormHandler::Field::Text->apply_traits( 'Another::Trait' );
  83. =head2 Select lists
  84. If you want to set the default value of a select field to 0, you can just
  85. use 'default' on the field:
  86. has_field 'license' => ( default => 0 );
  87. If there is logic involved, you can use a 'default_<field_name>' method:
  88. sub default_license {
  89. my ( $self, $field, $item ) = @_;
  90. return 0 unless $item && $item->license_id;
  91. return $item->license_id;
  92. }
  93. If the table defining the choices for a select list doesn't include
  94. a 'no choice' choice, you can set 'empty_select' in your field if you
  95. are using FormHandler rendering:
  96. has_field 'subject_class' => ( type => 'Select',
  97. empty_select => '--- Choose Subject Class ---' );
  98. Or you can do in a template:
  99. [% f = form.field('subject_class') %]
  100. <select id="select_sc" name="[% f.name %]">
  101. <option value="">--- Choose Subject Class---</option>
  102. [% FOR option IN f.options %]
  103. <option value="[% option.value %]"
  104. [% IF option.value == f.fif %]selected="selected"[% END %]>
  105. [% option.label | html %]</option>
  106. [% END %]
  107. </select>
  108. You can create a custom select list in an 'options_' method:
  109. sub options_country {
  110. my $self = shift;
  111. return unless $self->schema;
  112. my @rows =
  113. $self->schema->resultset( 'Country' )->
  114. search( {}, { order_by => ['rank', 'country_name'] } )->all;
  115. return [ map { $_->digraph, $_->country_name } @rows ];
  116. }
  117. =head2 The database and FormHandler forms
  118. If you have to process the input data before saving to the database, and
  119. this is something that would be useful in other places besides your form,
  120. you should do that processing in the DBIx::Class result class.
  121. If the pre-processing is only relevant to HTML form input, you might want
  122. to do it in the form by setting a flag to prevent database updates, performing
  123. the pre-processing, and then updating the database yourself.
  124. has_field 'my_complex_field' => ( type => 'Text', noupdate => 1 );
  125. The 'noupdate' flag is set in order to skip an attempt to update the database
  126. for this field (it would not be necessary if the field doesn't actually exist
  127. in the database...). You can process the input for the non-updatable field
  128. field in a number of different places, depending on what is most logical.
  129. Some of the choices are:
  130. 1) validate (for the form or field)
  131. 2) validate_model
  132. 3) update_model
  133. When the field is flagged 'writeonly', the value from the database will not
  134. be used to fill in the form (put in the C<< $form->fif >> hash, or the
  135. field C<< $field->fif >>), but a value entered in the form WILL be used
  136. to update the database.
  137. If you want to enter fields from an additional table that is related to
  138. this one in a 'single' relationship, you can use the DBIx::Class 'proxy'
  139. feature to create accessors for those fields.
  140. =head2 Set up form base classes or roles for your application
  141. You can add whatever attributes you want to your form classes. Maybe you
  142. want to save a title, or a particular navigation widget. You could even
  143. save bits of text, or retrieve them from the database.
  144. package MyApp::Form::Base;
  145. use Moose;
  146. extends 'HTML::FormHandler::Model::DBIC';
  147. has 'title' => ( isa => 'Str', is => 'rw' );
  148. has 'nav_bar' => ( isa => 'Str', is => 'rw' );
  149. has_block 'reg_header' => ( tag => 'fieldset', label => 'Registration form',
  150. content => 'We take your membership seriously...' );
  151. sub summary {
  152. my $self = shift;
  153. my $schema = $self->schema;
  154. my $text = $schema->resultset('Summary')->find( ... )->text;
  155. return $text;
  156. }
  157. 1;
  158. Then:
  159. package MyApp::Form::Whatsup;
  160. use Moose;
  161. extends 'MyApp::Form::Base';
  162. has '+title' => ( default => 'This page is an example of what to expect...' );
  163. has '+nav_bar' => ( default => ... );
  164. ...
  165. 1;
  166. And in the template:
  167. <h1>[% form.title %]</h1>
  168. [% form.nav_bar %]
  169. [% form.block('reg_header')->render %]
  170. <p><b>Summary: </b>[% form.summary %]</p>
  171. Or you can make these customizations Moose roles.
  172. package MyApp::Form::Role::Base;
  173. use Moose::Role;
  174. ...
  175. package MyApp::Form::Whatsup;
  176. use Moose;
  177. with 'MyApp::Form::Role::Base';
  178. ...
  179. =head2 Split up your forms into reusable pieces
  180. An address field:
  181. package Form::Field::Address;
  182. use HTML::FormHandler::Moose;
  183. extends 'HTML::FormHandler::Field::Compound';
  184. has_field 'street';
  185. has_field 'city';
  186. has_field 'state' => ( type => 'Select', options_method => \&options_state );
  187. has_field 'zip' => ( type => '+Zip' );
  188. sub options_state {
  189. ...
  190. }
  191. no HTML::FormHandler::Moose;
  192. 1;
  193. A person form that includes an address field:
  194. package Form::Person;
  195. use HTML::FormHandler::Moose;
  196. extends 'HTML::FormHandler';
  197. has '+widget_name_space' => ( default => sub {['Form::Field']} );
  198. has_field 'name';
  199. has_field 'telephone';
  200. has_field 'email' => ( type => 'Email' );
  201. has_field 'address' => ( type => 'Address' );
  202. sub validate_name {
  203. ....
  204. }
  205. no HTML::FormHandler::Moose;
  206. 1;
  207. Or you can use roles;
  208. package Form::Role::Address;
  209. use HTML::FormHandler::Moose::Role;
  210. has_field 'street';
  211. has_field 'city';
  212. has_field 'state' => ( type => 'Select' );
  213. has_field 'zip' => ( type => '+Zip' );
  214. sub options_state {
  215. ...
  216. }
  217. no HTML::FormHandler::Moose::Role;
  218. 1;
  219. You could make roles that are collections of validations:
  220. package Form::Role::Member;
  221. use Moose::Role;
  222. sub check_zip {
  223. ...
  224. }
  225. sub check_email {
  226. ...
  227. }
  228. 1;
  229. And if the validations apply to fields with different names, specify the
  230. 'validate_method' on the fields:
  231. with 'Form::Role::Member';
  232. has_field 'zip' => ( type => 'Integer', validate_method => \&check_zip );
  233. =head2 Access a user record in the form
  234. You might need the user_id to create specialized select lists, or do other form processing. Add a user_id attribute to your form:
  235. has 'user_id' => ( isa => 'Int', is => 'rw' );
  236. Then pass it in when you process the form:
  237. $form->process( item => $item, params => $c->req->parameters, user_id => $c->user->user_id );
  238. =head2 Handle extra database fields
  239. If there is another database field that needs to be updated when a row is
  240. created, add an attribute to the form, and then process it with
  241. C< before 'update_model' >.
  242. In the form:
  243. has 'hostname' => ( isa => 'Int', is => 'rw' );
  244. before 'update_model' => sub {
  245. my $self = shift;
  246. $self->item->hostname( $self->hostname );
  247. };
  248. Then just use an additional parameter when you create/process your form:
  249. $form->process( item => $item, params => $params, hostname => $c->req->host );
  250. Some kinds of DB relationships need to have primary keys which might be more easily
  251. set in the update_model method;
  252. sub update_model {
  253. my $self = shift;
  254. my $values = $self->values;
  255. $values->{some_field}->{some_key} = 'some_value';
  256. $self->_set_value($values);
  257. $self->next::method;
  258. }
  259. If you need to access a database field in order to create the value for a
  260. form field you can use a C< default_* > method.
  261. sub default_myformfield {
  262. my ($self, $field, $item) = @_;
  263. return unless defined $item;
  264. my $databasefield = $item->databasefield;
  265. my $value = ... # do stuff
  266. return $value;
  267. }
  268. =head2 Additional changes to the database
  269. If you want to do additional database updates besides the ones that FormHandler
  270. does for you, the best solution would generally be to add the functionality to
  271. your result source or resultset classes, but if you want to do additional updates
  272. in a form you should use an 'around' method modifier and a transaction:
  273. around 'update_model' => sub {
  274. my $orig = shift;
  275. my $self = shift;
  276. my $item = $self->item;
  277. $self->schema->txn_do( sub {
  278. $self->$orig(@_);
  279. <perform additional updates>
  280. });
  281. };
  282. =head2 Doing cross validation in roles
  283. In a role that handles a number of different fields, you may want to
  284. perform cross validation after the individual fields are validated.
  285. In the form you could use the 'validate' method, but that doesn't help
  286. if you want to keep the functionality packaged in a role. Instead you
  287. can use the 'after' method modifier on the 'validate' method:
  288. package MyApp::Form::Roles::DateFromTo;
  289. use HTML::FormHandler::Moose::Role;
  290. has_field 'date_from' => ( type => 'Date' );
  291. has_field 'date_to' => ( type => 'Date' );
  292. after 'validate' => sub {
  293. my $self = shift;
  294. $self->field('date_from')->add_error('From date must be before To date')
  295. if $self->field('date_from')->value gt $self->field('date_to')->value;
  296. };
  297. =head2 Changing required flag
  298. Sometimes a field is required in one situation and not required in another.
  299. You can use a method modifier before 'validate_form':
  300. before 'validate_form' => sub {
  301. my $self = shift;
  302. my $required = 0;
  303. $required = 1
  304. if( $self->params->{field_name} eq 'something' );
  305. $self->field('some_field')->required($required);
  306. };
  307. This happens before the fields contain input or values, so you would need to
  308. look at the param value. If you need the validated value, it might be better
  309. to do these sort of checks in the form's 'validate' routine.
  310. sub validate {
  311. my $self = shift;
  312. $self->field('dependent_field')->add_error("Field is required")
  313. if( $self->field('some_field')->value eq 'something' &&
  314. !$self->field('dependent_field')->has_value);
  315. }
  316. In a Moose role you would need to use a method modifier instead.
  317. after 'validate' => sub { ... };
  318. Don't forget the dependency list, which is used for cases where if any of one
  319. of a group of fields has a value, all of the fields are required.
  320. =head2 Supply an external coderef for validation
  321. There are situations in which you need to use a subroutine for validation
  322. which is not logically part of the form. It's possible to pass in a context
  323. or other sort of pointer and call the routine in the form's validation
  324. routine, but that makes the architecture muddy and is not a clear separation
  325. of concerns.
  326. This is an example of how to supply a coderef when constructing the form that
  327. performs validation and can be used to set an appropriate error
  328. using L<Moose::Meta::Attribute::Native::Trait::Code>.
  329. (Thanks to Florian Ragwitz for this excellent idea...)
  330. Here's the form:
  331. package SignupForm;
  332. use HTML::FormHandler::Moose;
  333. extends 'HTML::FormHandler';
  334. has check_name_availability => (
  335. traits => ['Code'],
  336. isa => 'CodeRef',
  337. required => 1,
  338. handles => { name_available => 'execute', },
  339. );
  340. has_field 'name';
  341. has_field 'email';
  342. sub validate {
  343. my $self = shift;
  344. my $name = $self->value->{name};
  345. if ( defined $name && length $name && !$self->name_available($name) ) {
  346. $self->field('name')->add_error('That name is taken already');
  347. }
  348. }
  349. 1;
  350. And here's where the coderef is passed in to the form.
  351. package MyApp::Signup;
  352. use Moose;
  353. has 'form' => ( is => 'ro', builder => 'build_form' );
  354. sub build_form {
  355. my $self = shift;
  356. return SignupForm->new(
  357. {
  358. check_name_availability => sub {
  359. my $name = shift;
  360. return $self->username_available($name);
  361. },
  362. }
  363. );
  364. }
  365. sub username_available {
  366. my ( $self, $name ) = @_;
  367. # perform some sort of username availability checks
  368. }
  369. 1;
  370. =head2 Example of a form with custom database interface
  371. The default DBIC model requires that the form structure match the database
  372. structure. If that doesn't work - you need to present the form in a different
  373. way - you may need to fudge it by creating your own 'init_object' and doing
  374. the database updates in the 'update_model' method.
  375. Here is a working example for a 'family' object (equivalent to a 'user'
  376. record') that has a relationship to permission type roles in a relationship
  377. 'user_roles'.
  378. package My::Form::AdminRoles;
  379. use HTML::FormHandler::Moose;
  380. extends 'HTML::FormHandler';
  381. has 'schema' => ( is => 'ro', required => 1 ); # Note 1
  382. has '+widget_wrapper' => ( default => 'None' ); # Note 2
  383. has_field 'admin_roles' => ( type => 'Repeatable' ); # Note 3
  384. has_field 'admin_roles.family' => ( type => 'Hidden' ); # Note 4
  385. has_field 'admin_roles.family_id' => ( type => 'PrimaryKey' ); # Note 5
  386. has_field 'admin_roles.admin_flag' => ( type => 'Boolean', label => 'Admin' );
  387. # Note 6
  388. sub init_object {
  389. my $self = shift;
  390. my @is_admin;
  391. my @is_not_admin;
  392. my $active_families = $self->schema->resultset('Family')->search( { active => 1 } );
  393. while ( my $fam = $active_families->next ) {
  394. my $admin_flag =
  395. $fam->search_related('user_roles', { role_id => 2 } )->count > 0 ? 1 : 0;
  396. my $family_name = $fam->name1 . ", " . $fam->name2;
  397. my $elem = { family => $family_name, family_id => $fam->family_id,
  398. admin_flag => $admin_flag };
  399. if( $admin_flag ) {
  400. push @is_admin, $elem;
  401. }
  402. else {
  403. push @is_not_admin, $elem;
  404. }
  405. }
  406. # Note 7
  407. # sort into admin flag first, then family_name
  408. @is_admin = sort { $a->{family} cmp $b->{family} } @is_admin;
  409. @is_not_admin = sort { $a->{family} cmp $b->{family} } @is_not_admin;
  410. return { admin_roles => [@is_admin, @is_not_admin] };
  411. }
  412. # Note 8
  413. sub update_model {
  414. my $self = shift;
  415. my $families = $self->schema->resultset('Family');
  416. my $family_roles = $self->value->{admin_roles};
  417. foreach my $elem ( @{$family_roles} ) {
  418. my $fam = $families->find( $elem->{family_id} );
  419. my $has_admin_flag = $fam->search_related('user_roles', { role_id => 2 } )->count > 0;
  420. if( $elem->{admin_flag} == 1 && !$has_admin_flag ) {
  421. $fam->create_related('user_roles', { role_id => 2 } );
  422. }
  423. elsif( $elem->{admin_flag} == 0 && $has_admin_flag ) {
  424. $fam->delete_related('user_roles', { role_id => 2 } );
  425. }
  426. }
  427. }
  428. Note 1: This form creates its own 'schema' attribute. You could inherit from
  429. L<HTML::FormHandler::Model::DBIC>, but you won't be using its update code, so
  430. it wouldn't add much.
  431. Note 2: The form will be displayed with a template that uses 'bare' form input
  432. fields, so 'widget_wrapper' is set to 'None' to skip wrapping the form inputs with
  433. divs or table elements.
  434. Note 3: This form consists of an array of elements, so there will be a single
  435. Repeatable form field with subfields. If you wanted to use automatic rendering, you would
  436. also need to create a 'submit' field, but in this case it will just be done
  437. in the template.
  438. Note 4: This field is actually going to be used for display purposes only, but it's
  439. a hidden field because otherwise the information would be lost when displaying
  440. the form from parameters. For this case there is no real 'validation' so it
  441. might not be necessary, but it would be required if the form needed to be
  442. re-displayed with error messages.
  443. Note 5: The 'family_id' is the primary key field, necessary for updating the
  444. correct records.
  445. Note 6: 'init_object' method: This is where the initial object is created, which
  446. takes the place of a database row for form creation.
  447. Note 7: The entries with the admin flag turned on are sorted into the beginning
  448. of the list. This is entirely a user interface choice.
  449. Note 8: 'update_model' method: This is where the database updates are performed.
  450. The Template Toolkit template for this form:
  451. <h1>Update admin status for members</h1>
  452. <form name="adminroles" method="POST" action="[% c.uri_for('admin_roles') %]">
  453. <input class="submit" name="submit" value="Save" type="submit">
  454. <table border="1">
  455. <th>Family</th><th>Admin</th>
  456. [% FOREACH f IN form.field('admin_roles').sorted_fields %]
  457. <tr>
  458. <td><b>[% f.field('family').fif %]</b>[% f.field('family').render %]
  459. [% f.field('family_id').render %]</td><td> [% f.field('admin_flag').render %]</td>
  460. </tr>
  461. [% END %]
  462. </table>
  463. <input class="submit" name="submit" value="Save" type="submit">
  464. </form
  465. The form is rendered in a simple table, with each field rendered using the
  466. automatically installed rendering widgets with no wrapper (widget_wrapper => 'None').
  467. There are two hidden fields here, so what is actually seen is two columns, one with
  468. the user (family) name, the other with a checkbox showing whether the user has
  469. admin status. Notice that the 'family' field information is rendered twice: once
  470. as a hidden field that will allow it to be preserved in params, once as a label.
  471. The Catalyst controller action to execute the form:
  472. sub admin_roles : Local {
  473. my ( $self, $c ) = @_;
  474. my $schema = $c->model('DB')->schema;
  475. my $form = My::Form::AdminRoles->new( schema => $schema );
  476. $form->process( params => $c->req->params );
  477. # re-process if form validated to reload from db and re-sort
  478. $form->process( params => {}) if $form->validated;
  479. $c->stash( form => $form, template => 'admin/admin_roles.tt' );
  480. return;
  481. }
  482. Rather than redirect to some other page after saving the form, the form is redisplayed.
  483. If the form has been validated (i.e. the 'update_model' method has been run), the
  484. 'process' call is run again in order to re-sort the displayed list with admin users at
  485. the top. That could have also been done in the 'update_model' method.
  486. =head2 A form that takes a resultset, with custom update_model
  487. For updating a Repeatable field that is filled from a Resultset, and not a
  488. relationship on a single row. Creates a 'resultset' attribute to pass in
  489. a resultset. Massages the data into an array that's pointed to by an
  490. 'employers' hash key, and does the reverse in the 'update_model' method.
  491. Yes, it's a kludge, but it could be worse. If you want to implement a more
  492. general solution, patches welcome.
  493. package Test::Resultset;
  494. use HTML::FormHandler::Moose;
  495. extends 'HTML::FormHandler::Model::DBIC';
  496. has '+item_class' => ( default => 'Employer' );
  497. has 'resultset' => ( isa => 'DBIx::Class::ResultSet', is => 'rw',
  498. trigger => sub { shift->set_resultset(@_) } );
  499. sub set_resultset {
  500. my ( $self, $resultset ) = @_;
  501. $self->schema( $resultset->result_source->schema );
  502. }
  503. sub init_object {
  504. my $self = shift;
  505. my $rows = [$self->resultset->all];
  506. return { employers => $rows };
  507. }
  508. has_field 'employers' => ( type => 'Repeatable' );
  509. has_field 'employers.employer_id' => ( type => 'PrimaryKey' );
  510. has_field 'employers.name';
  511. has_field 'employers.category';
  512. has_field 'employers.country';
  513. sub update_model {
  514. my $self = shift;
  515. my $values = $self->values->{employers};
  516. foreach my $row (@$values) {
  517. delete $row->{employer_id} unless defined $row->{employer_id};
  518. $self->resultset->update_or_create( $row );
  519. }
  520. }
  521. =head2 Server-provided dynamic value for field
  522. There are many different ways to provide values for fields. Default values can be
  523. statically provided in the form with the 'default' attribute on the field, with
  524. a default_<field_name> method in the form, with an init_object/item, and with
  525. 'default_over_obj' if you have both an item/init_object and want to provide a
  526. default.
  527. has_field 'foo' => ( default => 'my_default' );
  528. has_field 'foo' => ( default_over_obj => 'my_default' );
  529. sub default_foo { 'my_default' }
  530. ..
  531. $form->process( init_object => { foo => 'my_default } );
  532. $form->process( item => <object with $obj->foo method to provide default> );
  533. If you want to change the default for the field at run time, there are a number
  534. of options.
  535. You can set the value in the init_object or item before doing process:
  536. my $foo_value = 'some calculated value';
  537. $form->process( init_object => { foo => $foo_value } );
  538. You can use 'update_field_list' or 'defaults' on the 'process' call:
  539. $form->process( update_field_list => { foo => { default => $foo_value } } );
  540. -- or --
  541. $form->process( defaults => { foo => $foo_value } );
  542. You can set a Moose attribute in the form class, and set the default in a
  543. default_<field_name> method:
  544. package My::Form;
  545. use HTML::FormHandler::Moose;
  546. extends 'HTML::Formhandler';
  547. has 'form_id' => ( isa => 'Str', is => 'rw' );
  548. has_field 'foo';
  549. sub default_foo {
  550. my $self = shift;
  551. return $self->form_id;
  552. }
  553. ....
  554. $form->process( form_id => 'my_form', params => $params );
  555. You can set a Moose attribute in the form class and set it in an update_fields
  556. method:
  557. sub update_fields {
  558. my $self = shift;
  559. $self->field('foo')->default('my_form');
  560. }
  561. =head2 Static form, dynamic field IDs
  562. The problem: you have a form that will be used in multiple places on a page, but you
  563. want to use a static form instead of doing 'new' for each. You can pass a form name in
  564. on the process call and use 'html_prefix' in the form:
  565. $form->process( name => '...', params => {} );
  566. But the field 'id' attribute has already been constructed and doesn't change.
  567. Solution: apply a role to the base field class to replace the 'id' getter for the 'id'
  568. attribute with a method which constructs the 'id' dynamically. Since the role is
  569. being applied to the base field class, you can't just use 'sub id', because the
  570. 'id' method defined by the 'id' attribute has precedence. So create an 'around'
  571. method modifier that replaces it in the role.
  572. package My::DynamicFieldId;
  573. use Moose::Role;
  574. around 'id' => sub {
  575. my $orig = shift;
  576. my $self = shift;
  577. my $form_name = $self->form->name;
  578. return $form_name . "." . $self->full_name;
  579. };
  580. package My::CustomIdForm;
  581. use HTML::FormHandler::Moose;
  582. extends 'HTML::FormHandler';
  583. has '+html_prefix' => ( default => 1 );
  584. has '+field_traits' => ( default => sub { ['My::DynamicFieldId'] } );
  585. has_field 'foo';
  586. has_field 'bar';
  587. =head2 Create different field IDs
  588. Use 'build_id_method' to give your fields a different format 'id':
  589. package MyApp::CustomId;
  590. use HTML::FormHandler::Moose;
  591. extends 'HTML::FormHandler';
  592. has '+update_field_list' => ( default =>
  593. sub { { all => { build_id_method => \&custom_id } } } );
  594. has_field 'foo' => ( type => 'Compound' );
  595. has_field 'foo.one';
  596. has_field 'foo.two';
  597. has_field 'foo.three';
  598. sub custom_id {
  599. my $self = shift;
  600. my $full_name = $self->full_name;
  601. $full_name =~ s/\./_/g;
  602. return $full_name;
  603. }
  604. The above method provides IDs of "foo_two" and "foo_three" instead of
  605. "foo.two" and "foo.three".
  606. =cut