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