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