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