1package Form::Sensible::Reflector; 2use Moose; 3use namespace::autoclean; 4use Carp; 5 6our $VERSION = "0.01"; 7eval $VERSION; 8 9# ABSTRACT: A simple reflector class for Form::Sensible 10 11=head1 $self->with_trigger 12 13Add a submit button to the form. Defaults to 0. 14 15=cut 16 17#has 'with_trigger' => ( 18# is => 'rw', 19# lazy => 1, 20# default => 0, 21#); 22 23sub reflect_from { 24 my ( $self, $handle, $options) = @_; 25 26 my $form; 27 if (exists($options->{'form'})) { 28 if ( ref($options->{'form'}) eq 'HASH' ) { 29 $form = $self->create_form_object($handle, $options->{'form'}); 30 } elsif ( ref($options->{'form'}) && 31 UNIVERSAL::can($options->{'form'}, 'isa') && 32 $options->{'form'}->isa('Form::Sensible::Form') ) { 33 34 $form = $options->{'form'}; 35 } 36 else { 37 croak "form element provided in options, but it's not a form or a hash. What am I supposed to do with it?"; 38 } 39 } else { 40 if ( exists( $options->{'form_name'} ) ) { 41 $form = $self->create_form_object($handle, { name => $options->{'form_name'} }); 42 } else { 43 $form = $self->create_form_object($handle, undef); 44 } 45 } 46 47 48 my @allfields = $self->get_all_field_definitions($handle, $options, $form); 49 50 foreach my $field_def (@allfields) { 51 $form->add_field( $field_def ); 52 } 53 54 if (exists($options->{'with_trigger'}) && $options->{'with_trigger'}) { 55 $form->add_field(Form::Sensible::Field::Trigger->new( name => 'submit' )); 56 } 57 return $self->finalize_form($form, $handle); 58} 59 60sub get_all_field_definitions { 61 my ($self, $handle, $options, $form) = @_; 62 63 my @allfields; 64 my @fields = $self->get_fieldnames( $form, $handle ); 65 66 #my @definitions; 67 if ( exists( $options->{'fieldname_filter'} ) 68 && ref( $options->{'fieldname_filter'} ) eq 'CODE' ) 69 { 70 @fields = $options->{'fieldname_filter'}->(@fields); 71 } 72 73# this little chunk of code walks a fieldmap, if provided, and ensures that there 74# is a map entry for every field we know about. If none was provided, it creates 75# one for the field set to undef - which means do not add the field to the form. 76 77 my $fieldmap = { map { $_ => $_ } @fields }; 78 if ( exists( $options->{'fieldname_map'} ) 79 && ref( $options->{'fieldname_map'} ) eq 'HASH' ) 80 { 81 foreach my $field (@fields) { 82 if ( exists( $options->{'fieldname_map'}{$field} ) ) { 83 $fieldmap->{$field} = $options->{'fieldname_map'}{$field}; 84 } else { 85 $fieldmap->{$field} = undef; 86 } 87 } 88 } 89 90 my $additionalfields = {}; 91 92 ## copy any additional_fields provided so that we can process them later. 93 ## we have to do this because we modify our $additionalfields hash as we work on it, so we don't want 94 ## just a ref to what was provided. Deleting other people's data is unkind. 95 96 if (exists($options->{'additional_fields'}) && ref($options->{'additional_fields'}) eq 'ARRAY') { 97 foreach my $additional_field (@{$options->{'additional_fields'}}) { 98 $additionalfields->{$additional_field->{'name'}} = $additional_field; 99 push @fields, $additional_field->{'name'}; 100 } 101 } 102 103 foreach my $fieldname (@fields) { 104 my $new_fieldname = $fieldname; 105 if (exists($fieldmap->{$fieldname})) { 106 $new_fieldname = $fieldmap->{$fieldname}; 107 } 108 #warn "Processing: " . $fieldname . " as " . $new_fieldname; 109 110 if (defined($new_fieldname) || exists($additionalfields->{$new_fieldname} )) { 111 my $field_def; 112 if (exists($additionalfields->{$new_fieldname})) { 113 $field_def = $additionalfields->{$new_fieldname}; 114 delete($additionalfields->{$new_fieldname}); 115 } else { 116 $field_def = $self->get_field_definition( $form, $handle, $fieldname ); 117 next unless defined $field_def; 118 } 119 $field_def->{name} = $new_fieldname; 120 push @allfields, $field_def; 121 } 122 } 123 124 return @allfields; 125} 126 127sub create_form_object { 128 my ($self, $handle, $form_options) = @_; 129 130 if (!defined($form_options)) { 131 croak "No form provided, and no form name provided. Give me something to work with?"; 132 } 133 return Form::Sensible::Form->new($form_options); 134} 135 136sub finalize_form { 137 my ($self, $form, $handle) = @_; 138 139 return $form; 140} 141 142__PACKAGE__->meta->make_immutable; 1431; 144 145=head1 NAME 146 147Form::Sensible::Reflector - A base class for writing Form::Sensible reflectors. 148 149=cut 150 151=head1 SYNOPSIS 152 153 my $reflector = Form::Sensible::Reflector::SomeSubclass->new(); 154 155 my $generated_form = $reflector->reflect_from($data_source, $options); 156 157=head1 DESCRIPTION 158 159A Reflector in Form::Sensible is a class that inspects a data source and 160creates a form based on what it finds there. In other words it creates a form 161that 'reflects' the data elements found in the data source. 162 163A good example of this would be to create forms based on a DBIx::Class 164result_source (or table definition.) Using the DBIC reflector, you could 165create form for editing a user's profile information simply by passing the 166User result_source into the reflector. 167 168This module is a base class for writing reflectors, meaning you do not use 169this class directly. Instead you use one of the subclasses that deal with your 170data source type. 171 172=head1 USAGE 173 174 my $reflector = Form::Sensible::Form::Reflector::SomeSubclass->new(); 175 176 my $generated_form = $reflector->reflect_from($data_source, $options); 177 178By default, a Reflector will create a new form using the exact fields found 179within the datasource. It is possible, however, to adjust this behavior 180using the C<$options> hashref passed to the C<reflect_from> call. 181 182=head2 Adjusting the parameters of your new form 183 184 my $generated_form = $reflector->reflect_from($data_source, 185 { 186 form => { 187 name => 'profile_form', 188 validation => { 189 code => sub { ... } 190 } 191 } 192 }); 193 194If you want to adjust the parameters of the new form, you can provide a hashref 195in the C<< $options->{form} >> that will be passed to the 196C<< Form::Sensible::Form->new() >> call. 197 198=head2 Providing your own form 199 200 $reflector->reflect_from($data_source, 201 { 202 form => $my_existing_form_object 203 } 204 ); 205 206If you do not want to create a new form, but instead want the fields appended 207to an existing form, you can provide an existing form object in the options 208hash ( C<< $options->{form} >> ) 209 210=head2 Adding additional fields 211 212 $reflector->reflect_from($data_source, 213 { 214 additional_fields => [ 215 { 216 field_class => 'Text', 217 name => 'process_token', 218 render_hints => { 219 field_type => 'hidden', 220 } 221 }, 222 { 223 field_class => 'Trigger', 224 name => 'submit' 225 } 226 ] 227 } 228 229This allows you to add fields to your form in addition to the ones provided by 230your data source. It also allows you to override your data source, as any 231additional field with the same name as a reflected field will take precedence 232over the reflected field. This is also a good way to automatically add triggers 233to your form, such as a 'submit' or 'save' button. 234 235B<NOTE:> The reflector base class used to add a submit button automatically. The 236additional_fields mechanism replaces that functionality. This means your reflector 237call needs to add the submit button, as shown above, or it needs to be added 238programmatically later. 239 240=head2 Changing field order 241 242 $reflector->reflect_from($data_source, 243 { 244 ## sort fields alphabetically 245 fieldname_filter => sub { 246 return sort(@_); 247 }, 248 } 249 ); 250 251If you are unhappy with the order that your fields are displaying in you can 252adjust it by providing a subroutine in C<< $options->{'fieldname_filter'} >>. 253The subroutine takes the list of fields as returned by C<< get_fieldnames() >> 254and should return an array (not an array ref) of the fields in the new order. 255Note that you can also remove fields this way. Note also that no checking 256is done to verify that the fieldnames you return are valid, if you return 257any fields that were not in the original array, you are likely to cause an 258exception when the field definition is created. 259 260=head2 Changing field names 261 262$reflector->reflect_from($data_source, 263 { 264 ## change 'logon' field to be 'username' in the form 265 ## and other related adjustments. 266 fieldname_map => { 267 logon => 'username', 268 pass => 'password', 269 address => 'email', 270 home_num => 'phone', 271 parent_account => undef, 272 }, 273 } 274 ); 275 276By default, the C<Form::Sensible> field names are exactly the same as the data 277source's field names. If you would rather not expose your internal field names 278or have other reason to change them, you can provide a 279C<< $options->{'fieldname_map'} >> hashref to change them on the fly. The 280C<fieldname_map> is simply a mapping between the original field name and the 281C<Form::Sensible> field name you would like it to use. If you use this method 282you must provide a mapping for B<ALL> fields as a missing field (or a field 283with an undef value) is treated as a request to remove the field from the form 284entirely. 285 286=head1 CREATING YOUR OWN REFLECTOR 287 288Creating a new reflector class is extraordinarily simple. All you need to do 289is create a subclass of Form::Sensible::Reflector and then create two 290subroutines: C<get_fieldnames> and C<get_field_definition>. 291 292As you might expect, C<get_fieldnames> should return an array containing the 293names of the fields that are to be created. C<get_field_definition> is then 294called for each field to be created and should return a hashref representing 295that field suitable for passing to the 296L<Form::Sensible::Field|Form::Sensible::Field/"METHODS">'s 297C<create_from_flattened> method. 298 299Note that in both cases, the contents of C<$datasource> are specific to your 300reflector subclass and are not inspected in any way by the base class. 301 302=head2 Subclass Boilerplate 303 304 package My::Reflector; 305 use Moose; 306 use namespace::autoclean; 307 extends 'Form::Sensible::Reflector'; 308 309 sub get_fieldnames { 310 my ($self, $form, $datasource) = @_; 311 my @fieldnames; 312 313 foreach my $field ($datasource->the_way_to_get_all_your_fields()) { 314 push @fieldnames, $field->name; 315 } 316 return @fieldnames; 317 } 318 319 sub get_field_definition { 320 my ($self, $form, $datasource, $fieldname) = @_; 321 322 my $field_definition = { 323 name => $fieldname 324 }; 325 326 ## inspect $datasource's $fieldname and add things to $field_definition 327 328 return $field_definition; 329 } 330 331Note that while the C<$form> that your field will likely be added to is 332available for inspection, your reflector should NOT make changes to the passed 333form. It is present for inspection purposes only. If your module DOES have a 334reason to look at C<$form>, be aware that in some cases, such as when only the 335field definitions are requested, C<$form> will be null. Your reflector should 336do the sensible thing in this case, namely, not crash. 337 338=head2 Customizing the forms your reflector creates 339 340If you need to customize the form object that your reflector will return, 341there are two methods that Form::Sensible::Reflector will look for. You only 342need to provide these in your subclass if you need to modify the form object 343itself. If not, the default behaviors will work fine. The first is 344C<create_form_object> which C<Form::Sensible::Reflector> calls in order to 345instantiate a form object. It should return an instantiated 346L<Form::Sensible::Form> object. The default C<create_form_object> method 347simply passes the provided arguments to the 348L<Form::Sensible::Form/"METHODS">'s C<new> call: 349 350 sub create_form_object { 351 my ($self, $handle, $form_options) = @_; 352 353 return Form::Sensible::Form->new($form_options); 354 } 355 356Note that this will B<NOT> be called if the user provides a form object, so if 357special adjustments are absolutely required, you should consider making those 358changes using the C<finalize_form> method described below. 359 360The second method is C<finalize_form>. This method is called after the 361form has been created and all the fields have been added to the form. This 362allows you to do any final form customization prior to the form actually 363being used. This is a good way to add whole-form validation, for example: 364 365 sub finalize_form { 366 my ($self, $form, $handle) = @_; 367 368 return $form; 369 } 370 371Note that the C<finalize_form> call must return a form object. Most of the 372time this will be the form object passed to the method call. The return 373value of C<finalize_form> is what is returned to the user calling C<reflect_from>. 374 375=head2 Author's note 376 377This is a base class to write reflectors for things like, configuration files, 378or my favorite, a database schema. 379 380The idea is to give you something that creates a form from some other source 381that already defines form-like properties, ie a database schema that already 382has all the properties and fields a form would need. 383 384I personally hate dealing with forms that are longer than a search field or 385login form, so this really fits into my style. 386 387=head1 AUTHORS 388 389Devin Austin <dhoss@cpan.org> 390Jay Kuri <jayk@cpan.org> 391 392=cut 393 394=head1 ACKNOWLEDGEMENTS 395 396Jay Kuri <jayk@cpan.org> for his awesome Form::Sensible library and helping me 397get this library in tune with it. 398 399=cut 400 401=head1 SEE ALSO 402 403L<Form::Sensible> 404L<Form::Sensible> Wiki: L<http://wiki.catalyzed.org/cpan-modules/form-sensible> 405L<Form::Sensible> Discussion: L<http://groups.google.com/group/formsensible> 406 407=cut 408