blib/lib/Class/DBI/FormBuilder.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 65 | 723 | 8.9 |
branch | 2 | 358 | 0.5 |
condition | 0 | 135 | 0.0 |
subroutine | 18 | 86 | 20.9 |
pod | 27 | 27 | 100.0 |
total | 112 | 1329 | 8.4 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Class::DBI::FormBuilder; | ||||||
2 | |||||||
3 | 31 | 31 | 27166 | use warnings; | |||
31 | 71 | ||||||
31 | 1786 | ||||||
4 | 31 | 31 | 172 | use strict; | |||
31 | 65 | ||||||
31 | 1295 | ||||||
5 | 31 | 31 | 179 | use Carp(); | |||
31 | 60 | ||||||
31 | 547 | ||||||
6 | |||||||
7 | 31 | 31 | 171 | use List::Util(); | |||
31 | 52 | ||||||
31 | 5399 | ||||||
8 | 31 | 31 | 52181 | use CGI::FormBuilder 3; | |||
31 | 1260674 | ||||||
31 | 1493 | ||||||
9 | 31 | 31 | 29519 | use Class::DBI::FormBuilder::Meta::Table; | |||
31 | 120 | ||||||
31 | 335 | ||||||
10 | |||||||
11 | 31 | 31 | 50772 | use UNIVERSAL::require; | |||
31 | 62910 | ||||||
31 | 1411 | ||||||
12 | |||||||
13 | 31 | 31 | 1036 | use constant ME => 0; | |||
31 | 59 | ||||||
31 | 2334 | ||||||
14 | 31 | 31 | 160 | use constant THEM => 1; | |||
31 | 61 | ||||||
31 | 3638 | ||||||
15 | 31 | 31 | 167 | use constant FORM => 2; | |||
31 | 61 | ||||||
31 | 1534 | ||||||
16 | 31 | 31 | 171 | use constant FIELD => 3; | |||
31 | 70 | ||||||
31 | 1407 | ||||||
17 | 31 | 31 | 184 | use constant COLUMN => 4; | |||
31 | 63 | ||||||
31 | 1586 | ||||||
18 | |||||||
19 | 31 | 31 | 190 | use base 'Class::Data::Inheritable'; | |||
31 | 90 | ||||||
31 | 55111 | ||||||
20 | |||||||
21 | our $VERSION = '0.483'; | ||||||
22 | |||||||
23 | # process_extras *must* come 2nd last | ||||||
24 | our @BASIC_FORM_MODIFIERS = qw( pks options file timestamp text process_extras final ); | ||||||
25 | |||||||
26 | # C::FB sometimes gets confused when passed CDBI::Column objects as field names, | ||||||
27 | # hence all the map {''.$_} column filters. Some of them are probably unnecessary, | ||||||
28 | # but I need to track down which. UPDATE: the dev version now uses map { $_->name } | ||||||
29 | # everywhere. | ||||||
30 | |||||||
31 | # CDBI has accessor_name *and* mutator_name methods, so potentially, each column could | ||||||
32 | # have 2 methods to get/set its values, neither of which are the column's name. | ||||||
33 | |||||||
34 | # Column objects can be queried for these method names: $col->accessor and $col->mutator | ||||||
35 | |||||||
36 | # Not sure yet what to do about caller-supplied column names. | ||||||
37 | |||||||
38 | # General strategy: don't stringify anything until sending stuff to CGI::FB, at which point: | ||||||
39 | # 1. stringify all values | ||||||
40 | # 2. test field names to see if they are (CDBI column) objects, and if so, extract the | ||||||
41 | # appropriate accessor or mutator name | ||||||
42 | |||||||
43 | # UPDATE: forms should be built with $column->name as the field name, because in general | ||||||
44 | # form submissions will need to do both get and set operations. So the form handling | ||||||
45 | # methods should assume forms supply column names, and should look up column mutator/accessor | ||||||
46 | # as appropriate. | ||||||
47 | |||||||
48 | our %ValidMap = ( varchar => 'VALUE', | ||||||
49 | char => 'VALUE', # includes MySQL enum and set - UPDATE - not since 0.41 | ||||||
50 | |||||||
51 | enum => 'VALUE', | ||||||
52 | set => 'VALUE', | ||||||
53 | |||||||
54 | blob => 'VALUE', # includes MySQL text | ||||||
55 | text => 'VALUE', | ||||||
56 | |||||||
57 | integer => 'INT', | ||||||
58 | bigint => 'INT', | ||||||
59 | smallint => 'INT', | ||||||
60 | tinyint => 'INT', | ||||||
61 | int => 'INT', | ||||||
62 | |||||||
63 | date => 'VALUE', | ||||||
64 | time => 'VALUE', | ||||||
65 | datetime => 'VALUE', | ||||||
66 | |||||||
67 | # normally you want to skip validating a timestamp column... | ||||||
68 | #timestamp => 'VALUE', | ||||||
69 | |||||||
70 | double => 'NUM', | ||||||
71 | float => 'NUM', | ||||||
72 | decimal => 'NUM', | ||||||
73 | numeric => 'NUM', | ||||||
74 | ); | ||||||
75 | |||||||
76 | __PACKAGE__->mk_classdata( field_processors => {} ); | ||||||
77 | __PACKAGE__->mk_classdata( post_processors => {} ); | ||||||
78 | |||||||
79 | { | ||||||
80 | # field_processors | ||||||
81 | my $built_ins = { # default in form_pks | ||||||
82 | HIDDEN => [ '+HIDDEN', '+VALUE' ], | ||||||
83 | |||||||
84 | '+HIDDEN' => sub { $_[FORM]->field( name => $_[FIELD], | ||||||
85 | type => 'hidden', | ||||||
86 | ) }, | ||||||
87 | |||||||
88 | VALUE => '+VALUE', | ||||||
89 | |||||||
90 | '+VALUE' => sub | ||||||
91 | { | ||||||
92 | my $value; | ||||||
93 | |||||||
94 | my $accessor = $_[COLUMN]->accessor; | ||||||
95 | |||||||
96 | eval { $value = $_[THEM]->$accessor if ref( $_[THEM] ) }; | ||||||
97 | |||||||
98 | if ( $@ ) | ||||||
99 | { | ||||||
100 | die sprintf "Error running +VALUE on '%s' field: '%s' (value: '%s'): $@", | ||||||
101 | $_[THEM], $_[COLUMN]->accessor, defined $value ? $value : 'undef'; | ||||||
102 | } | ||||||
103 | |||||||
104 | $value = ''.$value if defined $value; # CGI::FB chokes on objects | ||||||
105 | |||||||
106 | if ( ! defined $value ) | ||||||
107 | { | ||||||
108 | # if the column can be NULL, and the value is undef, we have no way of | ||||||
109 | # knowing whether the value has never been set, or has been set to NULL | ||||||
110 | if ( ! $_[ME]->table_meta( $_[THEM] )->column( $_[FIELD] )->nullable ) | ||||||
111 | { | ||||||
112 | # but if the column can not be NULL, and the value is undef, | ||||||
113 | # set it to the default for the column | ||||||
114 | $value = $_[ME]->table_meta( $_[THEM] )->column( $_[FIELD] )->default; | ||||||
115 | } | ||||||
116 | } | ||||||
117 | |||||||
118 | $_[FORM]->field( name => $_[FIELD], | ||||||
119 | value => $value, | ||||||
120 | ); | ||||||
121 | }, | ||||||
122 | |||||||
123 | TIMESTAMP => 'READONLY', | ||||||
124 | |||||||
125 | DISABLED => [ '+DISABLED', '+VALUE' ], | ||||||
126 | |||||||
127 | '+DISABLED' => sub { $_[FORM]->field( name => $_[FIELD], | ||||||
128 | disabled => 1, | ||||||
129 | class => 'Disabled', | ||||||
130 | ) }, | ||||||
131 | |||||||
132 | READONLY => [ '+READONLY', '+VALUE' ], | ||||||
133 | |||||||
134 | '+READONLY' => sub { $_[FORM]->field( name => $_[FIELD], | ||||||
135 | readonly => 1, | ||||||
136 | class => 'ReadOnly', | ||||||
137 | ) }, | ||||||
138 | |||||||
139 | FILE => [ '+FILE', '+VALUE' ], | ||||||
140 | |||||||
141 | '+FILE' => sub | ||||||
142 | { | ||||||
143 | my $value = $_[THEM]->get( $_[FIELD] ) if ref( $_[THEM] ); | ||||||
144 | |||||||
145 | $_[FORM]->field( name => $_[FIELD], | ||||||
146 | type => 'file', | ||||||
147 | ); | ||||||
148 | }, | ||||||
149 | |||||||
150 | # default in form_options | ||||||
151 | OPTIONS_FROM_DB => [ '+OPTIONS_FROM_DB', '+VALUE' ], | ||||||
152 | |||||||
153 | '+OPTIONS_FROM_DB' => sub | ||||||
154 | { | ||||||
155 | my ( $series, $multiple ) = | ||||||
156 | $_[ME]->table_meta( $_[THEM] )->column( $_[FIELD] )->options; | ||||||
157 | |||||||
158 | return unless @$series; | ||||||
159 | |||||||
160 | $_[FORM]->field( name => $_[FIELD], | ||||||
161 | options => $series, | ||||||
162 | multiple => $multiple, | ||||||
163 | ); | ||||||
164 | }, | ||||||
165 | |||||||
166 | '+REQUIRED' => sub { $_[FORM]->field( name => $_[FIELD], | ||||||
167 | required => 1, | ||||||
168 | ) }, | ||||||
169 | |||||||
170 | '+NULL' => sub {}, | ||||||
171 | |||||||
172 | '+ADD_FIELD' => sub { $_[FORM]->field( name => $_[FIELD], | ||||||
173 | # need to set something to vivify the field | ||||||
174 | required => 0, | ||||||
175 | ) }, | ||||||
176 | |||||||
177 | }; | ||||||
178 | |||||||
179 | __PACKAGE__->field_processors( $built_ins ); | ||||||
180 | } | ||||||
181 | |||||||
182 | { | ||||||
183 | # post processors - note that the calling code is responsible for loading prerequisites | ||||||
184 | # of a processor e.g. HTML::Tree | ||||||
185 | my $built_ins = { | ||||||
186 | PrettyPrint => sub | ||||||
187 | { | ||||||
188 | my ( $me, $form, $render, undef, %args ) = @_; | ||||||
189 | |||||||
190 | # the is a trick to force HTML::TB to put the | ||||||
191 | # noscript in the body and not in the head | ||||||
192 | my $html_in = '' . $render->( $form, %args ); | ||||||
193 | |||||||
194 | my $tree = HTML::TreeBuilder->new; | ||||||
195 | |||||||
196 | $tree->store_comments( 1 ); | ||||||
197 | #$tree->ignore_unknown( 0 ); | ||||||
198 | $tree->no_space_compacting( 1 ); | ||||||
199 | #$tree->warn( 1 ); | ||||||
200 | |||||||
201 | $tree->parse( $html_in ); | ||||||
202 | $tree->eof; | ||||||
203 | |||||||
204 | my $html_out = $tree->guts->as_HTML( undef, ' ', {} ); | ||||||
205 | |||||||
206 | $tree->delete; | ||||||
207 | |||||||
208 | # clean up after the trick, and remove the outer div | ||||||
209 | # added by the guts() call (which removed html-head-body implicit tags) | ||||||
210 | $html_out =~ s'^ \s* \s* ''; |
||||||
211 | $html_out =~ s'$''; | ||||||
212 | |||||||
213 | return $html_out; | ||||||
214 | }, | ||||||
215 | |||||||
216 | # Duplicates => sub ... # removed after revision 368 | ||||||
217 | |||||||
218 | NoTextAreas => sub | ||||||
219 | { | ||||||
220 | my ( $me, $form, $render, undef, %args ) = @_; | ||||||
221 | |||||||
222 | foreach my $field ( $form->field ) | ||||||
223 | { | ||||||
224 | $field->type( 'text' ) if $field->type eq 'textarea'; | ||||||
225 | } | ||||||
226 | |||||||
227 | return $render->( $form, %args ); | ||||||
228 | }, | ||||||
229 | |||||||
230 | }; | ||||||
231 | |||||||
232 | __PACKAGE__->post_processors( $built_ins ); | ||||||
233 | } | ||||||
234 | |||||||
235 | sub import | ||||||
236 | { | ||||||
237 | 31 | 31 | 125 | my ( $class, %args ) = @_; | |||
238 | |||||||
239 | 31 | 231 | my $caller = caller(0); | ||||
240 | |||||||
241 | 31 | 50 | 2780 | $caller->can( 'form_builder_defaults' ) || $caller->mk_classdata( 'form_builder_defaults', {} ); | |||
242 | |||||||
243 | # replace CGI::FB's render() method with a hookable version | ||||||
244 | { | ||||||
245 | 31 | 793 | my $render = \&CGI::FormBuilder::render; | ||||
31 | 115 | ||||||
246 | |||||||
247 | my $hookable_render = sub | ||||||
248 | { | ||||||
249 | 0 | 0 | 0 | my ( $form, %args ) = @_; | |||
250 | |||||||
251 | 0 | 0 | 0 | 0 | if ( my $post_processor = delete( $args{post_process} ) || $form->__cdbi_original_args__->{post_process} ) | ||
252 | { | ||||||
253 | # the pp can mess with the form, then render it (as in the else clause below), then mess | ||||||
254 | # with the HTML, before returning the HTML | ||||||
255 | 0 | 0 | my $pp_args = $form->__cdbi_original_args__->{post_process_args}; | ||||
256 | |||||||
257 | 0 | 0 | 0 | my $pp = ref( $post_processor ) eq 'CODE' ? $post_processor : $class->post_processors->{ $post_processor }; | |||
258 | |||||||
259 | 0 | 0 | return $pp->( $class, $form, $render, $pp_args, %args ); | ||||
260 | } | ||||||
261 | else | ||||||
262 | { | ||||||
263 | 0 | 0 | return $render->( $form, %args ); | ||||
264 | } | ||||||
265 | 31 | 168 | }; | ||||
266 | |||||||
267 | 31 | 31 | 242 | no warnings 'redefine'; | |||
31 | 64 | ||||||
31 | 3667 | ||||||
268 | 31 | 151 | *CGI::FormBuilder::render = $hookable_render; | ||||
269 | } | ||||||
270 | |||||||
271 | # To support subclassing, store the FB (sub)class on the caller, and use that whenever we need | ||||||
272 | # to call an internal method on the CDBI::FB class | ||||||
273 | # i.e. say $them->__form_builder_subclass__ instead of __PACKAGE__ | ||||||
274 | 31 | 155 | $caller->mk_classdata( __form_builder_subclass__ => $class ); | ||||
275 | |||||||
276 | # _col_name_from_mutator_or_object() needs a cache of mutator_name => column_name | ||||||
277 | # on each CDBI class. Note that this accessor is used in a slightly unusual way, | ||||||
278 | # by including a key on the CDBI class. Otherwise, lookups on one class could | ||||||
279 | # fall through to an inherited map, rather than the map for the class we're | ||||||
280 | # interested in. So the map is only stored on $caller. | ||||||
281 | 31 | 720 | $caller->mk_classdata( __mutator_to_name__ => {} ); | ||||
282 | |||||||
283 | 31 | 720 | my @export = qw( as_form | ||||
284 | search_form | ||||||
285 | |||||||
286 | as_form_with_related | ||||||
287 | |||||||
288 | as_multiform | ||||||
289 | create_from_multiform | ||||||
290 | |||||||
291 | update_or_create_from_form | ||||||
292 | |||||||
293 | update_from_form_with_related | ||||||
294 | |||||||
295 | retrieve_from_form | ||||||
296 | search_from_form | ||||||
297 | search_like_from_form | ||||||
298 | search_where_from_form | ||||||
299 | |||||||
300 | find_or_create_from_form | ||||||
301 | retrieve_or_create_from_form | ||||||
302 | ); | ||||||
303 | |||||||
304 | 31 | 50 | 195 | if ( $args{BePoliteToFromForm} ) | |||
305 | { | ||||||
306 | 31 | 31 | 176 | no strict 'refs'; | |||
31 | 76 | ||||||
31 | 3684 | ||||||
307 | 0 | 0 | *{"$caller\::${_}_fb"} = \&{"${_}_form"} for qw( update_from create_from ); | ||||
0 | 0 | ||||||
0 | 0 | ||||||
308 | } | ||||||
309 | else | ||||||
310 | { | ||||||
311 | 31 | 133 | push @export, qw( update_from_form create_from_form ); | ||||
312 | } | ||||||
313 | |||||||
314 | 31 | 31 | 1024 | no strict 'refs'; | |||
31 | 78 | ||||||
31 | 10754 | ||||||
315 | 31 | 177 | *{"$caller\::$_"} = \&$_ for @export; | ||||
465 | 10897 | ||||||
316 | } | ||||||
317 | |||||||
318 | =head1 NAME | ||||||
319 | |||||||
320 | Class::DBI::FormBuilder - Class::DBI/CGI::FormBuilder integration | ||||||
321 | |||||||
322 | =head1 SYNOPSIS | ||||||
323 | |||||||
324 | |||||||
325 | package Film; | ||||||
326 | use strict; | ||||||
327 | use warnings; | ||||||
328 | |||||||
329 | use base 'Class::DBI'; | ||||||
330 | use Class::DBI::FormBuilder; | ||||||
331 | |||||||
332 | # for indented output: | ||||||
333 | # use Class::DBI::FormBuilder PrettyPrint => 'ALL'; | ||||||
334 | |||||||
335 | # POST all forms to server | ||||||
336 | Film->form_builder_defaults->{method} = 'post'; | ||||||
337 | |||||||
338 | # customise how some fields are built: | ||||||
339 | # 'actor' is a has_a field, and the | ||||||
340 | # related table has 1000's of rows, so we don't want the default popup widget, | ||||||
341 | # we just want to show the current value | ||||||
342 | Film->form_builder_defaults->{process_fields}->{actor} = 'VALUE'; | ||||||
343 | |||||||
344 | # 'trailer' stores an mpeg file, but CDBI::FB cannot automatically detect | ||||||
345 | # file upload fields, so need to tell it: | ||||||
346 | Film->form_builder_defaults->{process_fields}->{trailer} = 'FILE'; | ||||||
347 | |||||||
348 | # has_a fields will be automatically set to 'required'. Additional fields can be specified: | ||||||
349 | Film->form_builder_defaults->{required} = qw( foo bar ); | ||||||
350 | |||||||
351 | |||||||
352 | |||||||
353 | # In a nearby piece of code... | ||||||
354 | |||||||
355 | my $film = Film->retrieve( $id ); | ||||||
356 | print $film->as_form( params => $q )->render; # or $r if mod_perl | ||||||
357 | |||||||
358 | # For a search app: | ||||||
359 | my $search_form = Film->search_form; # as_form plus a few tweaks | ||||||
360 | |||||||
361 | |||||||
362 | # A fairly complete mini-app: | ||||||
363 | |||||||
364 | my $form = Film->as_form( params => $q ); # or $r if mod_perl | ||||||
365 | |||||||
366 | if ( $form->submitted and $form->validate ) | ||||||
367 | { | ||||||
368 | # whatever you need: | ||||||
369 | |||||||
370 | my $obj = Film->create_from_form( $form ); | ||||||
371 | my $obj = Film->update_from_form( $form ); | ||||||
372 | my $obj = Film->update_or_create_from_form( $form ); | ||||||
373 | my $obj = Film->retrieve_from_form( $form ); | ||||||
374 | |||||||
375 | my $iter = Film->search_from_form( $form ); | ||||||
376 | my $iter = Film->search_like_from_form( $form ); | ||||||
377 | my $iter = Film->search_where_from_form( $form ); | ||||||
378 | |||||||
379 | my $obj = Film->find_or_create_from_form( $form ); | ||||||
380 | my $obj = Film->retrieve_or_create_from_form( $form ); | ||||||
381 | |||||||
382 | print $form->confirm; | ||||||
383 | } | ||||||
384 | else | ||||||
385 | { | ||||||
386 | print $form->render; | ||||||
387 | } | ||||||
388 | |||||||
389 | # See CGI::FormBuilder docs and website for lots more information. | ||||||
390 | |||||||
391 | =head1 DESCRIPTION | ||||||
392 | |||||||
393 | B | ||||||
394 | accessors/mutators are different from the column name>. The documentation is also broken w.r.t. this. | ||||||
395 | |||||||
396 | This module creates a L |
||||||
397 | from an object, it populates the form fields with the object's values. | ||||||
398 | |||||||
399 | Column metadata and CDBI relationships are analyzed and the fields of the form are modified accordingly. | ||||||
400 | For instance, MySQL C |
||||||
401 | C |
||||||
402 | and C |
||||||
403 | are set as 'required' fields in create/update forms. | ||||||
404 | |||||||
405 | A demonstration app (using L |
||||||
406 | |||||||
407 | http://beerfb.riverside-cms.co.uk | ||||||
408 | |||||||
409 | =head1 Customising field construction | ||||||
410 | |||||||
411 | Often, the default behaviour will be unsuitable. For instance, a C |
||||||
412 | a related table with thousands of records. A popup widget with all these records is probably not useful. | ||||||
413 | Also, it will take a long time to build, so post-processing the form to re-design the field is a | ||||||
414 | poor solution. | ||||||
415 | |||||||
416 | Instead, you can pass an extra C |
||||||
417 | set it in C |
||||||
418 | |||||||
419 | Many of the internal routines use this mechanism for configuring fields. A manually set '+' | ||||||
420 | (basic) processor will be B |
||||||
421 | processor (no '+') will B |
||||||
422 | |||||||
423 | You can add your own processors to the internal table of processors - see C |
||||||
424 | |||||||
425 | =head2 process_fields | ||||||
426 | |||||||
427 | This is a hashref, with keys being field names. Values can be: | ||||||
428 | |||||||
429 | =over 4 | ||||||
430 | |||||||
431 | =item Name of a built-in | ||||||
432 | |||||||
433 | basic shortcut | ||||||
434 | ------------------------------------------------------------------------------- | ||||||
435 | +HIDDEN HIDDEN make the field hidden | ||||||
436 | +VALUE VALUE display the current value | ||||||
437 | +READONLY READONLY display the current value - not editable | ||||||
438 | +DISABLED DISABLED display the current value - not editable, not selectable, (not submitted?) | ||||||
439 | +FILE FILE build a file upload widget | ||||||
440 | +OPTIONS_FROM_DB OPTIONS_FROM_DB check if the column is constrained to a few values | ||||||
441 | +REQUIRED make the field required | ||||||
442 | +NULL no-op - useful for debugging | ||||||
443 | +ADD_FIELD add a new field to the form (only necessary if the field is empty) | ||||||
444 | TIMESTAMP used to process TIMESTAMP fields, defaults to DISABLED, but you can | ||||||
445 | easily replace it with a different behaviour | ||||||
446 | +SET_VALUE($value) set the value of the field to $value - DEPRECATED - use +SET_value | ||||||
447 | +SET_$foo($value) SET_$foo($value) set the $foo attribute of the field to $value | ||||||
448 | |||||||
449 | The 'basic' versions apply only their own modification. The 'shortcut' version also applies | ||||||
450 | the C<+VALUE> processor. | ||||||
451 | |||||||
452 | C |
||||||
453 | this explicitly, as it's already used internally. | ||||||
454 | |||||||
455 | The C<+ADD_FIELD> processor is only necessary if you need to add a new field to a form, but don't want to | ||||||
456 | use any of the other processors on it. | ||||||
457 | |||||||
458 | =item Reference to a subroutine, or anonymous coderef | ||||||
459 | |||||||
460 | The coderef will be passed the L |
||||||
461 | object, the L |
||||||
462 | named field. | ||||||
463 | |||||||
464 | =item Package name | ||||||
465 | |||||||
466 | Name of a package with a suitable C |
||||||
467 | the coderef. | ||||||
468 | |||||||
469 | =item Arrayref of the above | ||||||
470 | |||||||
471 | Applies each processor in order. | ||||||
472 | |||||||
473 | =back | ||||||
474 | |||||||
475 | The key C<__FINAL__> is reserved for C |
||||||
476 | field processor is set in C<__FINAL__>, then it will be applied to all fields, after all other | ||||||
477 | processors have run. | ||||||
478 | |||||||
479 | =head1 Customising C |
||||||
480 | |||||||
481 | C |
||||||
482 | The hook is a coderef, or the name of a built-in, supplied in the C |
||||||
483 | be set in the call to C |
||||||
484 | is passed the following arguments: | ||||||
485 | |||||||
486 | $class the CDBI::FormBuilder class or subclass | ||||||
487 | $form the CGI::FormBuilder form object | ||||||
488 | $render reference to &CGI::FormBuilder::render | ||||||
489 | $pp_args value of the post_process_args argument, or undef | ||||||
490 | %args the arguments used in the CGI::FormBuilder->new call | ||||||
491 | |||||||
492 | The coderef should return HTML markup for the form, probably by calling C<< $render->( $form, %args ) >>. | ||||||
493 | |||||||
494 | =over 4 | ||||||
495 | |||||||
496 | =item PrettyPrint | ||||||
497 | |||||||
498 | A pretty-printer coderef is available in the hashref of built-in post-processors: | ||||||
499 | |||||||
500 | my $pretty = Class::DBI::FormBuilder->post_processors->{PrettyPrint}; | ||||||
501 | |||||||
502 | So you can turn on pretty printing for a class by setting: | ||||||
503 | |||||||
504 | My::Class->form_builder_defaults->{post_process} = Class::DBI::FormBuilder->post_processors->{PrettyPrint}; | ||||||
505 | |||||||
506 | =item NoTextAreas | ||||||
507 | |||||||
508 | This post-processor ensures that any fields configured as C | ||||||
509 | field before rendering. | ||||||
510 | |||||||
511 | This might have been used for instance in the L |
||||||
512 | C
|
||||||
513 | an ugly hack that doesn't support post-processors. | ||||||
514 | |||||||
515 | =back | ||||||
516 | |||||||
517 | =head1 Plugins | ||||||
518 | |||||||
519 | C |
||||||
520 | load (via C |
||||||
521 | to load L |
||||||
522 | passing the CDBI class for whom the form has been constructed, the form, and a L |
||||||
523 | representing the field being processed. The plugin can use this information to modify the form, perhaps | ||||||
524 | adding extra fields, or controlling stringification, or setting up custom validation. Note that the name of | ||||||
525 | the form field should be retrieved from the field object as C<< $field->name >>, rather than relying | ||||||
526 | on C< $field > to stringify itself, because it will stringify to C<< $field->name_lc >>. | ||||||
527 | |||||||
528 | If no plugin is found, a fatal exception is thrown. If you have a situation where it would be useful to | ||||||
529 | simply stringify the object instead, let me know and I'll make this configurable. | ||||||
530 | |||||||
531 | =head1 Automatic validation setup | ||||||
532 | |||||||
533 | If you place a normal L |
||||||
534 | that spec will be used to configure validation. | ||||||
535 | |||||||
536 | If there is no spec in the method call or in C<< $class->form_builder_defaults->{validate} >>, then | ||||||
537 | validation will be configured automatically. The default configuration is pretty basic, but you can modify it | ||||||
538 | by placing settings in the C |
||||||
539 | |||||||
540 | =head2 Basic auto-validation | ||||||
541 | |||||||
542 | Given no validation options for a column in the C |
||||||
543 | will be taken from C<%Class::DBI::FormBuilder::ValidMap>. This maps SQL column types to the L |
||||||
544 | |||||||
545 | MySQL C |
||||||
546 | values. | ||||||
547 | |||||||
548 | Any column listed in C<< $class->form_builder_defaults->{options} >> will be set to validate those values. | ||||||
549 | |||||||
550 | =head2 Advanced auto-validation | ||||||
551 | |||||||
552 | The following settings can be placed in the C |
||||||
553 | C<< $class->form_builder_defaults->{auto_validate} >>). | ||||||
554 | |||||||
555 | =over 4 | ||||||
556 | |||||||
557 | =item validate | ||||||
558 | |||||||
559 | Specify validate types for specific columns: | ||||||
560 | |||||||
561 | validate => { username => [qw(nate jim bob)], | ||||||
562 | first_name => '/^\w+$/', # note the | ||||||
563 | last_name => '/^\w+$/', # single quotes! | ||||||
564 | email => 'EMAIL', | ||||||
565 | password => \&check_password, | ||||||
566 | confirm_password => { | ||||||
567 | javascript => '== form.password.value', | ||||||
568 | perl => 'eq $form->field("password")' | ||||||
569 | } | ||||||
570 | |||||||
571 | This option takes the same settings as the C |
||||||
572 | (i.e. the same as would otherwise go in the C |
||||||
573 | C<< $class->form_builder_defaults->{validate} >>). Settings here override any others. | ||||||
574 | |||||||
575 | =item columns | ||||||
576 | |||||||
577 | Alias for C |
||||||
578 | feels more comfortable. If you're used to using L |
||||||
579 | natural to use C |
||||||
580 | |||||||
581 | =item skip_columns | ||||||
582 | |||||||
583 | List of columns that will not be validated: | ||||||
584 | |||||||
585 | skip_columns => [ qw( secret_stuff internal_data ) ] | ||||||
586 | |||||||
587 | =item match_columns | ||||||
588 | |||||||
589 | Use regular expressions matching groups of columns to specify validation: | ||||||
590 | |||||||
591 | match_columns => { qr/(^(widget|burger)_size$/ => [ qw( small medium large ) ], | ||||||
592 | qr/^count_.+$/ => 'INT', | ||||||
593 | } | ||||||
594 | |||||||
595 | =item validate_types | ||||||
596 | |||||||
597 | Validate according to SQL data types: | ||||||
598 | |||||||
599 | validate_types => { date => \&my_date_checker, | ||||||
600 | } | ||||||
601 | |||||||
602 | Defaults are taken from the package global C<%TypesMap>. | ||||||
603 | |||||||
604 | =item match_types | ||||||
605 | |||||||
606 | Use a regular expression to map SQL data types to validation types: | ||||||
607 | |||||||
608 | match_types => { qr(date) => \&my_date_checker, | ||||||
609 | } | ||||||
610 | |||||||
611 | =item debug | ||||||
612 | |||||||
613 | Control how much detail to report (via C |
||||||
614 | info, and 2 for a list of each column's validation setting. | ||||||
615 | |||||||
616 | =item strict | ||||||
617 | |||||||
618 | If set to 1, will die if a validation setting cannot be determined for any column. | ||||||
619 | Default is to issue warnings and not validate these column(s). | ||||||
620 | |||||||
621 | =back | ||||||
622 | |||||||
623 | =head2 Validating relationships | ||||||
624 | |||||||
625 | Although it would be possible to retrieve the IDs of all objects for a related column and use these to | ||||||
626 | set up validation, this would rapidly become unwieldy for larger tables. Default validation will probably be | ||||||
627 | acceptable in most cases, as the column type will usually be some kind of integer. | ||||||
628 | |||||||
629 | =over 4 | ||||||
630 | |||||||
631 | =item timestamp | ||||||
632 | |||||||
633 | The default behaviour is to skip validating C |
||||||
634 | if the C |
||||||
635 | |||||||
636 | Note that C |
||||||
637 | |||||||
638 | =item Failures | ||||||
639 | |||||||
640 | The default mapping of column types to validation types is set in C<%Class::DBI::FormBulder::ValidMap>, | ||||||
641 | and is probably incomplete. If you come across any failures, you can add suitable entries to the hash before calling C |
||||||
642 | |||||||
643 | =back | ||||||
644 | |||||||
645 | =cut | ||||||
646 | |||||||
647 | =head1 Other features | ||||||
648 | |||||||
649 | =over 4 | ||||||
650 | |||||||
651 | =item Class::DBI::FromForm | ||||||
652 | |||||||
653 | If you want to use this module alongside L |
||||||
654 | load the module like so | ||||||
655 | |||||||
656 | use Class::DBI::FormBuilder BePoliteToFromForm => 1; | ||||||
657 | |||||||
658 | and C |
||||||
659 | |||||||
660 | You might want to do this if you have more complex validation requirements than L |
||||||
661 | |||||||
662 | =back | ||||||
663 | |||||||
664 | =head1 METHODS | ||||||
665 | |||||||
666 | Most of the methods described here are exported into the caller's namespace, except for the form modifiers | ||||||
667 | (see below), and a few others as documented. | ||||||
668 | |||||||
669 | =over 4 | ||||||
670 | |||||||
671 | =item new_field_processor( $processor_name, $coderef or package name ) | ||||||
672 | |||||||
673 | This method is called on C |
||||||
674 | object or subclass. | ||||||
675 | |||||||
676 | It installs a new field processor, which can then be referred to by name in C |
||||||
677 | rather than by passing a coderef. This method could also be used to replace the supplied built-in | ||||||
678 | field processors, for example to alter the default C |
||||||
679 | The new processor must either be a coderef, or the name of a package with a | ||||||
680 | suitable C |
||||||
681 | |||||||
682 | The code ref will be passed these arguments: | ||||||
683 | |||||||
684 | position argument | ||||||
685 | -------------------- | ||||||
686 | 0 name of the calling class (i.e. Class::DBI::FormBuilder or a subclass) | ||||||
687 | 1 Class::DBI object or class name | ||||||
688 | 2 CGI::FormBuilder form object | ||||||
689 | 3 name of the current field | ||||||
690 | 4 Class::DBI::Column object for the current field | ||||||
691 | |||||||
692 | The name of the current field is the name used on the form object, and is also the B |
||||||
693 | for the column on the CDBI object (which defaults to the name in the database, but can be different). | ||||||
694 | |||||||
695 | The column object is useful if the processor needs access to the value in the CDBI object, but the | ||||||
696 | mutator name is different from the column accessor e.g. see the C<+VALUE> processor. | ||||||
697 | |||||||
698 | =cut | ||||||
699 | |||||||
700 | # ----------------------------------------------------------------- field processor architecture ----- | ||||||
701 | |||||||
702 | # install a new default processor that can be referred to by name | ||||||
703 | sub new_field_processor | ||||||
704 | { | ||||||
705 | 0 | 0 | 1 | my ( $me, $p_name, $p ) = @_; | |||
706 | |||||||
707 | 0 | 0 | my $coderef = $p if ref( $p ) eq 'CODE'; | ||||
708 | |||||||
709 | 0 | 0 | unless ( $coderef ) | ||||
710 | { | ||||||
711 | 0 | 0 | $p->require || die "Error loading custom field processor package $p: $@"; | ||||
712 | |||||||
713 | 0 | 0 | UNIVERSAL::can( $p, 'field' ) or die "$p does not have a field() subroutine"; | ||||
714 | |||||||
715 | 31 | 31 | 169 | no strict 'refs'; | |||
31 | 55 | ||||||
31 | 477668 | ||||||
716 | 0 | $coderef = \&{"$p\::field"}; | |||||
0 | |||||||
717 | } | ||||||
718 | |||||||
719 | 0 | $me->field_processors->{ $p_name } = $coderef; | |||||
720 | } | ||||||
721 | |||||||
722 | # use a chain of processors to construct a field | ||||||
723 | sub _process_field | ||||||
724 | { | ||||||
725 | 0 | 0 | my ( $me, $them, $form, $field, $process ) = @_; | ||||
726 | |||||||
727 | # $field will normally be a CDBI column object, but can be a string | ||||||
728 | #my $field_name = ref $field ? $field->mutator : $field; | ||||||
729 | 0 | 0 | my $field_name = ref $field ? $field->name : $field; | ||||
730 | |||||||
731 | # some processors (e.g. +VALUE) need access to accessor name, not mutator name | ||||||
732 | #my $column = ref $field ? $field : $me->_column_from_mutator( $them, $field ); | ||||||
733 | 0 | 0 | my $column = ref $field ? $field : $them->find_column( $field ); | ||||
734 | |||||||
735 | 0 | my $chain = $me->_build_processor_chain( $process ); | |||||
736 | |||||||
737 | # pass the form to each sub in the chain and tweak the specified field | ||||||
738 | 0 | while ( my $p = $chain->() ) | |||||
739 | { | ||||||
740 | 0 | $p->( $me, $them, $form, $field_name, $column ); | |||||
741 | } | ||||||
742 | } | ||||||
743 | |||||||
744 | # returns an iterator | ||||||
745 | sub _build_processor_chain | ||||||
746 | { | ||||||
747 | 0 | 0 | my ( $me, $process ) = @_; | ||||
748 | |||||||
749 | 0 | my @agenda = ( $process ); | |||||
750 | |||||||
751 | # Expand each item on the agenda. Arrayrefs get listified and unshifted back | ||||||
752 | # on to the start of the agenda. Coderefs on the agenda are returned. Non-code scalars are | ||||||
753 | # looked up in the pre-processors dispatch table, or in another package, and | ||||||
754 | # unshifted onto the start of the agenda, because they may be pointing to | ||||||
755 | # further keys in the dispatch table. | ||||||
756 | 0 | my $chain; | |||||
757 | |||||||
758 | $chain = sub | ||||||
759 | { | ||||||
760 | 0 | 0 | my $next = pop( @agenda ); | ||||
761 | |||||||
762 | 0 | 0 | return unless $next; | ||||
763 | |||||||
764 | 0 | 0 | return $next if ref( $next ) eq 'CODE'; | ||||
765 | |||||||
766 | 0 | 0 | unshift @agenda, ref $next eq 'ARRAY' ? @$next : $me->_track_down( $next ); | ||||
767 | |||||||
768 | 0 | return $chain->(); | |||||
769 | 0 | }; | |||||
770 | |||||||
771 | 0 | return $chain; | |||||
772 | } | ||||||
773 | |||||||
774 | sub _track_down | ||||||
775 | { | ||||||
776 | 0 | 0 | my ( $me, $processor ) = @_; | ||||
777 | |||||||
778 | 0 | 0 | return $processor if ref( $processor ) eq 'CODE'; | ||||
779 | |||||||
780 | 0 | my $p = $me->field_processors->{ $processor }; | |||||
781 | |||||||
782 | # might be a coderef, might be another key | ||||||
783 | 0 | 0 | return $p if $p; | ||||
784 | |||||||
785 | # +SET_VALUE() special case - DEPRECATED in 0.41 | ||||||
786 | 0 | 0 | if ( $processor =~ /^\+SET_VALUE\(\s*(.*)\s*\)$/ ) | ||||
787 | { | ||||||
788 | 0 | my $value = $1; | |||||
789 | |||||||
790 | 0 | warn '+SET_VALUE($value) is deprecated - use +SET_value($value) instead'; | |||||
791 | |||||||
792 | 0 | 0 | $p = sub { $_[FORM]->field( name => $_[FIELD], | ||||
793 | value => $value, | ||||||
794 | ); | ||||||
795 | 0 | }; | |||||
796 | |||||||
797 | 0 | return $p; | |||||
798 | } | ||||||
799 | |||||||
800 | # +SET_$foo($bar) general special case | ||||||
801 | 0 | 0 | if ( $processor =~ /^(?:\+?)SET_(\w+)\(\s*(.*)\s*\)$/ ) | ||||
802 | { | ||||||
803 | 0 | my $attribute = $1; | |||||
804 | 0 | my $value = $2; | |||||
805 | |||||||
806 | 0 | 0 | $p = sub { $_[FORM]->field( name => $_[FIELD], | ||||
807 | $attribute => $value, | ||||||
808 | ); | ||||||
809 | 0 | }; | |||||
810 | |||||||
811 | 0 | return $p; | |||||
812 | } | ||||||
813 | |||||||
814 | # # +FIELD_PREFIX($prefix) | ||||||
815 | # if ( $processor =~ /^\+FIELD_PREFIX(\s*(.*)\s*\)$/ ) | ||||||
816 | # { | ||||||
817 | # my $prefix = $1; | ||||||
818 | # | ||||||
819 | # $p = sub { $_[FORM]->field( name => $_[FIELD], | ||||||
820 | # | ||||||
821 | # } | ||||||
822 | |||||||
823 | 0 | 0 | die "Unexpected ref: $processor (expected class name)" if ref $processor; | ||||
824 | |||||||
825 | # it's a field sub in another class | ||||||
826 | 0 | 0 | $processor->require or die "Couldn't load field processor package $processor: $@"; | ||||
827 | |||||||
828 | 0 | 0 | $p = $processor->can( 'field' ) || die "No field method in $processor"; | ||||
829 | |||||||
830 | 0 | return $p; | |||||
831 | } | ||||||
832 | |||||||
833 | # Combines automatic and custom processors. Custom processors are | ||||||
834 | # traversed until a 'stop' processor is found (a named processor without a leading '+'). | ||||||
835 | # If found, returns the custom set only. If no 'stop' processor is found, appends the | ||||||
836 | # custom set to the auto set. | ||||||
837 | sub _add_processors | ||||||
838 | { | ||||||
839 | 0 | 0 | my ( $me, $field, $pre_process, $auto ) = @_; | ||||
840 | |||||||
841 | # $field will usually be a CDBI column object | ||||||
842 | 0 | 0 | my $field_name = ref $field ? $field->mutator : $field; | ||||
843 | |||||||
844 | 0 | my $custom = $pre_process->{ $field_name }; | |||||
845 | |||||||
846 | #warn sprintf "Combining procs %s and %s\n", $auto || '', $custom || ''; | ||||||
847 | |||||||
848 | # I'd use xor if I had a one-liner that doesn't use the temp var | ||||||
849 | #my $only = $custom xor $auto; | ||||||
850 | #return $only if $only; | ||||||
851 | 0 | 0 | return $custom unless $auto; | ||||
852 | 0 | 0 | return $auto unless $custom; | ||||
853 | |||||||
854 | 0 | my $chain = $me->_build_named_processor_chain( $custom ); | |||||
855 | |||||||
856 | 0 | while ( my $name = $chain->() ) | |||||
857 | { | ||||||
858 | #warn "Checking custom processor $name for stop"; | ||||||
859 | #warn "Dropping automatic processors - found custom stop processor $name" if $name !~ /^\+/; | ||||||
860 | 0 | 0 | return $custom if $name !~ /^\+/; | ||||
861 | } | ||||||
862 | |||||||
863 | 0 | return [ $auto, $custom ]; # it's OK if either are already arrayrefs | |||||
864 | } | ||||||
865 | |||||||
866 | # only use this to look at the names, not to do any processing, because it throws away | ||||||
867 | # any processors that are not named | ||||||
868 | sub _build_named_processor_chain | ||||||
869 | { | ||||||
870 | 0 | 0 | my ( $me, $process ) = @_; | ||||
871 | |||||||
872 | 0 | my @agenda = ( $process ); | |||||
873 | |||||||
874 | # Expand each item on the agenda. Arrayrefs get listified and unshifted back | ||||||
875 | # on to the start of the agenda. Coderefs on the agenda are returned. Non-code scalars are | ||||||
876 | # looked up in the pre-processors dispatch table, or in another package, and | ||||||
877 | # unshifted onto the start of the agenda, because they may be pointing to | ||||||
878 | # further keys in the dispatch table. | ||||||
879 | 0 | my $chain; | |||||
880 | |||||||
881 | $chain = sub | ||||||
882 | { | ||||||
883 | 0 | 0 | my $next = pop( @agenda ); | ||||
884 | |||||||
885 | 0 | 0 | return unless $next; | ||||
886 | |||||||
887 | # if it's a coderef, drop it and move on to next item | ||||||
888 | 0 | 0 | return $chain->() if ref( $next ) eq 'CODE'; | ||||
889 | |||||||
890 | # if it's an arrayref, expand it onto the start of the agenda and move on | ||||||
891 | # to next item (i.e. first item in the arrayref) | ||||||
892 | 0 | 0 | if ( ref( $next ) eq 'ARRAY' ) | ||||
893 | { | ||||||
894 | 0 | unshift @agenda, @$next; | |||||
895 | 0 | return $chain->(); | |||||
896 | } | ||||||
897 | |||||||
898 | 0 | 0 | die "Unexpected ref for processor: $next" if ref $next; | ||||
899 | |||||||
900 | # It's a string | ||||||
901 | # if it's in the processors hash, then | ||||||
902 | # - check if it returns a coderef or an arrayref or a string when looked up | ||||||
903 | # - if a coderef, return the string | ||||||
904 | # - unshift anything else onto the agenda | ||||||
905 | 0 | 0 | if ( my $foo = $me->field_processors->{ $next } ) | ||||
906 | { | ||||||
907 | 0 | 0 | return $next if ref $foo eq 'CODE'; | ||||
908 | |||||||
909 | # it's a string or an arrayref | ||||||
910 | 0 | unshift @agenda, $foo; | |||||
911 | } | ||||||
912 | |||||||
913 | 0 | return $chain->(); | |||||
914 | 0 | }; | |||||
915 | |||||||
916 | 0 | return $chain; | |||||
917 | } | ||||||
918 | |||||||
919 | # ----------------------------------------------------------------- / field processor architecture ----- | ||||||
920 | |||||||
921 | # ----------------------------------------------------------------------- column meta data ----- | ||||||
922 | |||||||
923 | =item table_meta($them) | ||||||
924 | |||||||
925 | L |
||||||
926 | |||||||
927 | Returns a L |
||||||
928 | |||||||
929 | =cut | ||||||
930 | |||||||
931 | sub table_meta | ||||||
932 | { | ||||||
933 | 0 | 0 | 1 | my ($me, $them) = @_; | |||
934 | |||||||
935 | 0 | return Class::DBI::FormBuilder::Meta::Table->instance($them); | |||||
936 | } | ||||||
937 | |||||||
938 | # Return the class or object(s) associated with a field, if anything is associated. | ||||||
939 | # This can't go in table_meta because it can be called on objects (???) | ||||||
940 | sub _related | ||||||
941 | { | ||||||
942 | 0 | 0 | my ($me, $them, $field) = @_; | ||||
943 | |||||||
944 | 0 | my ($related_class, $rel_type) = $me->table_meta($them)->related_class_and_rel_type($field); | |||||
945 | |||||||
946 | 0 | 0 | return unless $related_class; | ||||
947 | |||||||
948 | 0 | 0 | return ($related_class, $rel_type) unless ref $them; | ||||
949 | |||||||
950 | 0 | 0 | my $related_meta = $them->meta_info( $rel_type => $field ) || | ||||
951 | die "No '$rel_type' meta for '$them', field '$field'"; | ||||||
952 | |||||||
953 | 0 | my $accessor = eval { $related_meta->accessor }; | |||||
0 | |||||||
954 | 0 | 0 | die "Error retrieving accessor in meta '$related_meta' for '$rel_type' field '$field' in '$them': $@" if $@; | ||||
955 | |||||||
956 | # multiple objects for has_many | ||||||
957 | 0 | my @related_objects = $them->$accessor; | |||||
958 | |||||||
959 | 0 | 0 | return ( $related_class, $rel_type ) unless @related_objects; | ||||
960 | 0 | 0 | return ( $related_objects[0], $rel_type ) if @related_objects == 1; | ||||
961 | 0 | return ( \@related_objects, $rel_type ); | |||||
962 | } | ||||||
963 | |||||||
964 | # ----------------------------------------------------------------------- / column meta data ----- | ||||||
965 | |||||||
966 | =back | ||||||
967 | |||||||
968 | =head2 Form generating methods | ||||||
969 | |||||||
970 | =over 4 | ||||||
971 | |||||||
972 | =item form_builder_defaults( %args ) | ||||||
973 | |||||||
974 | Stores default arguments. | ||||||
975 | |||||||
976 | =item as_form( %args ) | ||||||
977 | |||||||
978 | Builds a L |
||||||
979 | |||||||
980 | Takes default arguments from C |
||||||
981 | |||||||
982 | The optional hash of arguments is the same as for C |
||||||
983 | and will override any keys in C |
||||||
984 | |||||||
985 | The extra keys are documented in various places in this file - I'll gather them together here | ||||||
986 | over time. Extra keys include: | ||||||
987 | |||||||
988 | =over 4 | ||||||
989 | |||||||
990 | =item options_sorters | ||||||
991 | |||||||
992 | A hashref, keyed by field name, with values being coderefs that will be used to sort the list | ||||||
993 | of options generated for a C |
||||||
994 | |||||||
995 | The coderef will be passed pairs of options arrayrefs, and should return the standard Perl sort | ||||||
996 | codes (i.e. -1, 0, or 1). The first item in each arrayref is the value of the option, the second | ||||||
997 | is the label. | ||||||
998 | |||||||
999 | Note that the coderef should be prototyped ($$): | ||||||
1000 | |||||||
1001 | # sort by label, alphabetically | ||||||
1002 | $field_name => sub ($$) { $_[0]->[1] cmp $_[1]->[1] } | ||||||
1003 | |||||||
1004 | # sort by value, numerically | ||||||
1005 | $field_name => sub ($$) { $_[0]->[0] <=> $_[1]->[0] } | ||||||
1006 | |||||||
1007 | =back | ||||||
1008 | |||||||
1009 | Note that parameter merging is likely to become more sophisticated in future releases | ||||||
1010 | (probably copying the argument merging code from L |
||||||
1011 | itself). | ||||||
1012 | |||||||
1013 | =item search_form( %args ) | ||||||
1014 | |||||||
1015 | Build a form with inputs that can be fed to search methods (e.g. C |
||||||
1016 | For instance, all selects are multiple, fields that normally would be required | ||||||
1017 | are not, and C |
||||||
1018 | |||||||
1019 | B |
||||||
1020 | still configure validation settings using the standard L |
||||||
1021 | |||||||
1022 | In many cases, you will want to design your own search form, perhaps only searching | ||||||
1023 | on a subset of the available columns. Note that you can acheive that by specifying | ||||||
1024 | |||||||
1025 | fields => [ qw( only these fields ) ] | ||||||
1026 | |||||||
1027 | in the args. | ||||||
1028 | |||||||
1029 | The following search options are available. They are only relevant if processing | ||||||
1030 | via C |
||||||
1031 | |||||||
1032 | =over 4 | ||||||
1033 | |||||||
1034 | =item search_opt_cmp | ||||||
1035 | |||||||
1036 | Allow the user to select a comparison operator by passing an arrayref: | ||||||
1037 | |||||||
1038 | search_opt_cmp => [ ( '=', '!=', '<', '<=', '>', '>=', | ||||||
1039 | 'LIKE', 'NOT LIKE', 'REGEXP', 'NOT REGEXP', | ||||||
1040 | 'REGEXP BINARY', 'NOT REGEXP BINARY', | ||||||
1041 | ) ] | ||||||
1042 | |||||||
1043 | |||||||
1044 | Or, transparently set the search operator in a hidden field: | ||||||
1045 | |||||||
1046 | search_opt_cmp => 'LIKE' | ||||||
1047 | |||||||
1048 | =item search_opt_order_by | ||||||
1049 | |||||||
1050 | If true, will generate a widget to select (possibly multiple) columns to order the results by, | ||||||
1051 | with an C |
||||||
1052 | |||||||
1053 | If set to an arrayref, will use that to build the widget. | ||||||
1054 | |||||||
1055 | # order by any columns | ||||||
1056 | search_opt_order_by => 1 | ||||||
1057 | |||||||
1058 | # or just offer a few | ||||||
1059 | search_opt_order_by => [ 'foo', 'foo DESC', 'bar' ] | ||||||
1060 | |||||||
1061 | =back | ||||||
1062 | |||||||
1063 | =cut | ||||||
1064 | |||||||
1065 | sub as_form | ||||||
1066 | { | ||||||
1067 | 0 | 0 | 1 | my ( $them, %args_in ) = @_; | |||
1068 | |||||||
1069 | 0 | my $me = $them->__form_builder_subclass__; | |||||
1070 | |||||||
1071 | 0 | return scalar $me->_as_form( $them, %args_in ); | |||||
1072 | } | ||||||
1073 | |||||||
1074 | =begin notes | ||||||
1075 | |||||||
1076 | There seem to be several ways to approach this: | ||||||
1077 | |||||||
1078 | 1. Modify the original args, so that CGI::FB builds a single form with multiple sets of inputs. | ||||||
1079 | This seems difficult, but would result in a form that could be processed very easily. | ||||||
1080 | |||||||
1081 | 2. Build multiple forms, and use a custom javascript submit button to gather all their inputs and | ||||||
1082 | submit a single form. The js is tricky, and processing the input is not easy, because we don't | ||||||
1083 | have a server form to handle it. | ||||||
1084 | |||||||
1085 | 3. Use HTML::Tree to build a super-form from a standard form. Same problem with processing input as | ||||||
1086 | for #2. | ||||||
1087 | |||||||
1088 | 4. AJAX - instead of submitting all forms in one go (as in #2), submit each form individually. This | ||||||
1089 | would solve the problem of processing the submission, but requires a different architecture. | ||||||
1090 | |||||||
1091 | Seems to boil down to #1 or #3. | ||||||
1092 | |||||||
1093 | The problem with #1 is that after building the CGI::FB form, it is then passed through all the form | ||||||
1094 | modifiers, including any registered field processors. All of this would have to cope with modifying | ||||||
1095 | field names. But maybe that could be done via a final field modifier? | ||||||
1096 | |||||||
1097 | The problem with #3 is processing submissions, since the final form is never represented by a CGI::FB | ||||||
1098 | form. | ||||||
1099 | |||||||
1100 | UPDATE: the solution is: | ||||||
1101 | |||||||
1102 | 5. Build the individual forms, tweak their field names, then combine all the fields | ||||||
1103 | from all the forms into a single form. This works because most of the CGI::FB magic | ||||||
1104 | does not happen during form construction, but during calls made on the completed form. | ||||||
1105 | |||||||
1106 | =end notes | ||||||
1107 | |||||||
1108 | =cut | ||||||
1109 | |||||||
1110 | =item as_multiform | ||||||
1111 | |||||||
1112 | This method supports adding multiple related items to an object in a related class. Call this method | ||||||
1113 | on the class at the 'many' end of a C |
||||||
1114 | |||||||
1115 | foo | ||||||
1116 | bar | ||||||
1117 | baz | ||||||
1118 | |||||||
1119 | it builds a form with fields | ||||||
1120 | |||||||
1121 | R1__foo | ||||||
1122 | R1__bar | ||||||
1123 | R1__baz | ||||||
1124 | R2__foo | ||||||
1125 | R2__bar | ||||||
1126 | R2__baz | ||||||
1127 | etc. | ||||||
1128 | |||||||
1129 | Specify the number of duplicates in the C |
||||||
1130 | |||||||
1131 | Use C |
||||||
1132 | |||||||
1133 | See C |
||||||
1134 | |||||||
1135 | =cut | ||||||
1136 | |||||||
1137 | sub as_multiform | ||||||
1138 | { | ||||||
1139 | 0 | 0 | 1 | my ( $them, %args_in ) = @_; | |||
1140 | |||||||
1141 | 0 | my $me = $them->__form_builder_subclass__; | |||||
1142 | |||||||
1143 | 0 | 0 | my $how_many = delete $args_in{how_many} || die 'need to know how many to build'; | ||||
1144 | |||||||
1145 | 0 | my @forms; | |||||
1146 | |||||||
1147 | 0 | foreach my $fnum ( 1..$how_many ) | |||||
1148 | { | ||||||
1149 | 0 | my $prefix = "R$fnum\__"; | |||||
1150 | 0 | my $form = $them->as_form( %args_in ); | |||||
1151 | |||||||
1152 | 0 | my @fields = $form->fields; | |||||
1153 | |||||||
1154 | 0 | foreach my $field ( @fields ) | |||||
1155 | { | ||||||
1156 | # get the label before it changes | ||||||
1157 | 0 | my $label = $field->label; | |||||
1158 | 0 | my $name = $field->name; | |||||
1159 | 0 | $field->name( "$prefix${name}" ); | |||||
1160 | # put the label back | ||||||
1161 | 0 | $field->label( $label ); | |||||
1162 | } | ||||||
1163 | |||||||
1164 | # put a bit of space after the last field | ||||||
1165 | 0 | $fields[-1]->comment( ' ' ); |
|||||
1166 | |||||||
1167 | 0 | push @forms, $form; | |||||
1168 | } | ||||||
1169 | |||||||
1170 | 0 | return $me->_merge_forms( @forms ); | |||||
1171 | } | ||||||
1172 | |||||||
1173 | sub _merge_forms | ||||||
1174 | { | ||||||
1175 | 0 | 0 | my ( $me, @forms ) = @_; | ||||
1176 | |||||||
1177 | 0 | my $form = shift @forms; | |||||
1178 | |||||||
1179 | 0 | foreach my $additional_form ( @forms ) | |||||
1180 | { | ||||||
1181 | 0 | foreach my $field ( $additional_form->fields ) | |||||
1182 | { | ||||||
1183 | 0 | $field->_form( $form ); | |||||
1184 | |||||||
1185 | 0 | $form->{fieldrefs}{ $field->name } = $field; | |||||
1186 | |||||||
1187 | 0 | push @{ $form->{fields} }, $field; | |||||
0 | |||||||
1188 | } | ||||||
1189 | } | ||||||
1190 | |||||||
1191 | 0 | return $form; | |||||
1192 | } | ||||||
1193 | |||||||
1194 | sub _as_form | ||||||
1195 | { | ||||||
1196 | 0 | 0 | my ( $me, $them, %args_in ) = @_; | ||||
1197 | |||||||
1198 | # search_form does not (automatically) validate input | ||||||
1199 | 0 | my $skip_validation = delete $args_in{__SKIP_VALIDATION__}; | |||||
1200 | |||||||
1201 | 0 | my ( $orig, %args ) = $me->_get_args( $them, %args_in ); | |||||
1202 | |||||||
1203 | 0 | 0 | $me->_setup_auto_validation( $them, \%args ) unless $skip_validation; | ||||
1204 | |||||||
1205 | 0 | my $form = $me->_make_form( $them, $orig, %args ); | |||||
1206 | |||||||
1207 | 0 | 0 | return wantarray ? ( $form, %args ) : $form; | ||||
1208 | } | ||||||
1209 | |||||||
1210 | sub search_form | ||||||
1211 | { | ||||||
1212 | 0 | 0 | 1 | my ( $them, %args_in ) = @_; | |||
1213 | |||||||
1214 | 0 | my $me = $them->__form_builder_subclass__; | |||||
1215 | |||||||
1216 | 0 | 0 | my $cdbi_class = ref( $them ) || $them; | ||||
1217 | |||||||
1218 | 0 | $args_in{__SKIP_VALIDATION__}++; | |||||
1219 | |||||||
1220 | 0 | my ( $form, %args ) = $me->_as_form( $cdbi_class, %args_in ); | |||||
1221 | |||||||
1222 | # We need the names of two special fields and a regexp to recognize them | ||||||
1223 | 0 | my $order_by_field_name = 'search_opt_order_by'; | |||||
1224 | 0 | my $cmp_field_name = 'search_opt_cmp'; | |||||
1225 | 0 | my $regexp = qr/^(?:$order_by_field_name|$cmp_field_name)$/o; | |||||
1226 | |||||||
1227 | # make all selects multiple, no fields required unless explicitly set, | ||||||
1228 | # and change textareas back into text inputs | ||||||
1229 | 0 | 0 | my %force_required = map { $_ => 1 } @{ $args{required} || [] }; | ||||
0 | |||||||
0 | |||||||
1230 | 0 | foreach my $field ( $form->field ) | |||||
1231 | { | ||||||
1232 | 0 | 0 | next unless exists $form->field->{ $field }; | ||||
1233 | |||||||
1234 | # skip search controls | ||||||
1235 | 0 | 0 | next if $field =~ $regexp; | ||||
1236 | |||||||
1237 | 0 | 0 | $field->multiple( 1 ) if $field->options; | ||||
1238 | |||||||
1239 | 0 | 0 | $field->required( 0 ) unless $force_required{ $field }; | ||||
1240 | |||||||
1241 | 0 | 0 | $field->type( 'text' ) if $field->type eq 'textarea'; | ||||
1242 | |||||||
1243 | # some default field processors may set a value, which needs to be | ||||||
1244 | # removed on the search form | ||||||
1245 | #$field->value( undef ); # this requires CGI::FB 3.03 | ||||||
1246 | 0 | $form->field( name => $field->name, value => undef ); | |||||
1247 | } | ||||||
1248 | |||||||
1249 | # ----- customise the search ----- | ||||||
1250 | # For processing a submitted form, remember that the field _must_ be added to the form | ||||||
1251 | # so that its submitted value can be extracted in search_where_from_form() | ||||||
1252 | |||||||
1253 | # ----- order_by | ||||||
1254 | # this must come before adding any other fields, because the list of columns | ||||||
1255 | # is taken from the form (not the CDBI class/object) so we match whatever | ||||||
1256 | # column selection happened during form construction | ||||||
1257 | 0 | my %order_by_spec = ( # name => 'search_opt_order_by', | |||||
1258 | multiple => 1, | ||||||
1259 | ); | ||||||
1260 | |||||||
1261 | 0 | 0 | if ( my $order_by = delete $args{ $order_by_field_name } ) | ||||
1262 | { | ||||||
1263 | 0 | 0 | $order_by = [ map { ''.$_, "$_ DESC" } | ||||
0 | |||||||
1264 | 0 | 0 | grep { $_->type ne 'hidden' and $_ !~ $regexp } | ||||
1265 | $form->field | ||||||
1266 | ] | ||||||
1267 | unless ref $order_by; | ||||||
1268 | |||||||
1269 | 0 | $order_by_spec{options} = $order_by; | |||||
1270 | } | ||||||
1271 | |||||||
1272 | # ----- comparison operator | ||||||
1273 | 0 | 0 | my $cmp = delete( $args{ $cmp_field_name } ) || '='; | ||||
1274 | |||||||
1275 | 0 | my %cmp_spec; # = ( name => 'search_opt_cmp' ); | |||||
1276 | |||||||
1277 | 0 | 0 | if ( ref( $cmp ) ) | ||||
1278 | { | ||||||
1279 | 0 | $cmp_spec{options} = $cmp; | |||||
1280 | 0 | $cmp_spec{value} = $cmp->[0]; | |||||
1281 | #$cmp_spec{multiple} = 0; | ||||||
1282 | } | ||||||
1283 | else | ||||||
1284 | { | ||||||
1285 | 0 | $cmp_spec{value} = $cmp; | |||||
1286 | 0 | $cmp_spec{type} = 'hidden'; | |||||
1287 | } | ||||||
1288 | |||||||
1289 | # this is annoying... | ||||||
1290 | 0 | my %fields = map { ''.$_ => $_ } $form->field; | |||||
0 | |||||||
1291 | |||||||
1292 | # if the caller has passed in some custom settings, they will have caused the field to be | ||||||
1293 | # auto-vivified | ||||||
1294 | 0 | 0 | if ( my $cmp_field = $fields{ $cmp_field_name } ) | ||||
1295 | { | ||||||
1296 | # this (used to?) causes a warning when setting the value, which may mean the value has already been set before | ||||||
1297 | 0 | $cmp_field->$_( $cmp_spec{ $_ } ) for keys %cmp_spec; | |||||
1298 | } | ||||||
1299 | else | ||||||
1300 | # otherwise, we need to auto-vivify it now | ||||||
1301 | { | ||||||
1302 | 0 | $form->field( name => $cmp_field_name, %cmp_spec ); | |||||
1303 | } | ||||||
1304 | |||||||
1305 | 0 | 0 | if ( my $order_by_field = $fields{ $order_by_field_name } ) | ||||
1306 | { | ||||||
1307 | 0 | $order_by_field->$_( $order_by_spec{ $_ } ) for keys %order_by_spec; | |||||
1308 | } | ||||||
1309 | else | ||||||
1310 | { | ||||||
1311 | 0 | $form->field( name => $order_by_field_name, %order_by_spec ); | |||||
1312 | } | ||||||
1313 | |||||||
1314 | # ...why did this stop working? - I think because sometimes the fields are auto-vivified before getting | ||||||
1315 | # to this point, and that seems to be problem when setting the value | ||||||
1316 | #$form->field( %cmp_spec ); | ||||||
1317 | #$form->field( %order_by_spec ); | ||||||
1318 | |||||||
1319 | 0 | return $form; | |||||
1320 | } | ||||||
1321 | |||||||
1322 | # need to do much better argument merging | ||||||
1323 | sub _get_args | ||||||
1324 | { | ||||||
1325 | 0 | 0 | my ( $me, $them, %args_in ) = @_; | ||||
1326 | |||||||
1327 | #@{ $args_in{fields} } = map { ''.$_ } @{ $args_in{fields} } if $args_in{fields}; | ||||||
1328 | |||||||
1329 | # NOTE: this merging still means any custom processors for a given field, will replace all default | ||||||
1330 | # processors for that field, but at least we can mix some fields having default | ||||||
1331 | # processors, and others having custom ones. | ||||||
1332 | 0 | 0 | my $pre_process1 = $them->form_builder_defaults->{process_fields} || {}; | ||||
1333 | 0 | 0 | my $pre_process2 = delete( $args_in{process_fields} ) || {}; | ||||
1334 | 0 | my %pre_process = ( %$pre_process1, %$pre_process2 ); | |||||
1335 | |||||||
1336 | # merge sorters and remove from %args_in (although note that any default sorters will still | ||||||
1337 | # be present in %args) | ||||||
1338 | 0 | 0 | my %options_sorters = ( %{ $them->form_builder_defaults->{options_sorters} || {} }, | ||||
0 | 0 | ||||||
1339 | 0 | %{ delete( $args_in{options_sorters} ) || {} }, | |||||
1340 | ); | ||||||
1341 | |||||||
1342 | 0 | my %args = ( %{ $them->form_builder_defaults }, %args_in ); | |||||
0 | |||||||
1343 | |||||||
1344 | 0 | $args{process_fields} = \%pre_process; | |||||
1345 | |||||||
1346 | # take a copy, and make sure not to transform undef into [] | ||||||
1347 | 0 | 0 | my $original_fields = $args{fields} ? [ @{ $args{fields} } ] : undef; | ||||
0 | |||||||
1348 | |||||||
1349 | 0 | my %pk = map { $_ => $_ } $them->primary_columns; | |||||
0 | |||||||
1350 | |||||||
1351 | 0 | 0 | $args{fields} ||= [ grep { ! $pk{ $_ } } $me->table_meta( $them )->columns( 'All' ) ]; | ||||
0 | |||||||
1352 | |||||||
1353 | # convert anything referring to a column, into a CDBI column object | ||||||
1354 | 0 | 0 | 0 | $args{fields} = [ map { ref $_ ? $_ : $them->find_column( $_ ) || $_ } @{ $args{fields} } ]; | |||
0 | |||||||
0 | |||||||
1355 | |||||||
1356 | 0 | 0 | 0 | push( @{ $args{keepextras} }, values %pk ) unless ( $args{keepextras} && $args{keepextras} == 1 ); | |||
0 | |||||||
1357 | |||||||
1358 | # for objects, populate with data | ||||||
1359 | 0 | 0 | if ( ref $them ) | ||||
1360 | { | ||||||
1361 | # nb. can't simply say $proto->get( $_ ) because $_ may be an accessor installed by a relationship | ||||||
1362 | # (e.g. has_many) - get() only works with real columns. | ||||||
1363 | # Note that has_many and might_have and has_a fields are re-processed later (in form_* methods), | ||||||
1364 | # it might become necessary to filter them out here? | ||||||
1365 | 0 | 0 | my @values = eval { map { $them->$_ } # may return a scalar, undef, or object (or objects for has_many?) | ||||
0 | |||||||
0 | |||||||
1366 | 0 | map { ref $_ ? $_->accessor : $_ } | |||||
1367 | 0 | @{ $args{fields} } # may be strings or CDBI column objects | |||||
1368 | }; | ||||||
1369 | |||||||
1370 | 0 | 0 | die "Error populating values for $them from '@{ $args{fields} }': $@" if $@; | ||||
0 | |||||||
1371 | |||||||
1372 | 0 | 0 | $args{values} ||= \@values; | ||||
1373 | } | ||||||
1374 | |||||||
1375 | 0 | 0 | my %post_process = ( | ||||
0 | |||||||
1376 | post_process => delete( $args_in{post_process} ) || $them->form_builder_defaults->{post_process}, | ||||||
1377 | post_process_args => delete( $args_in{post_process_args} ) || $them->form_builder_defaults->{post_process_args}, | ||||||
1378 | ); | ||||||
1379 | |||||||
1380 | 0 | 0 | %post_process = () unless $post_process{post_process}; | ||||
1381 | |||||||
1382 | 0 | 0 | my $process_extras = delete( $args_in{process_extras} ) || []; | ||||
1383 | |||||||
1384 | # store a few CDBI::FB arguments that may be needed later | ||||||
1385 | 0 | my $orig = { fields => $original_fields, | |||||
1386 | %post_process, | ||||||
1387 | process_extras => $process_extras, | ||||||
1388 | options_sorters => \%options_sorters, | ||||||
1389 | }; | ||||||
1390 | |||||||
1391 | 0 | return $orig, %args; | |||||
1392 | } | ||||||
1393 | |||||||
1394 | sub _make_form | ||||||
1395 | { | ||||||
1396 | 0 | 0 | my ($me, $them, $orig, %args) = @_; | ||||
1397 | |||||||
1398 | 0 | 0 | my $pre_process = delete( $args{process_fields} ) || {}; | ||||
1399 | |||||||
1400 | 0 | my %clean_args = $me->_stringify_args(%args); | |||||
1401 | |||||||
1402 | 0 | my $form = CGI::FormBuilder->new(%clean_args); | |||||
1403 | |||||||
1404 | 0 | $form->{__cdbi_original_args__} = $orig; | |||||
1405 | |||||||
1406 | # this assumes meta_info only holds data on relationships | ||||||
1407 | 0 | foreach my $modify ( @BASIC_FORM_MODIFIERS, keys %{ $them->meta_info } ) | |||||
0 | |||||||
1408 | { | ||||||
1409 | 0 | my $form_modify = "form_$modify"; | |||||
1410 | |||||||
1411 | 0 | $me->$form_modify($them, $form, $pre_process); | |||||
1412 | } | ||||||
1413 | |||||||
1414 | 0 | return $form; | |||||
1415 | } | ||||||
1416 | |||||||
1417 | # If any columns are supplied as CDBI column objects, we need to change them into the appropriate | ||||||
1418 | # string, which is supplied by the mutator method on the column. | ||||||
1419 | # Also, CGI::FB does some argument pre-processing that chokes on objects, even if the objects can be | ||||||
1420 | # stringified, so values need to be stringified here. | ||||||
1421 | sub _stringify_args | ||||||
1422 | { | ||||||
1423 | 0 | 0 | my ( $me, %args ) = @_; | ||||
1424 | |||||||
1425 | #warn "Dirty args: " . Dumper( \%args ); | ||||||
1426 | |||||||
1427 | # fields - but this could also be a hashref? | ||||||
1428 | 0 | 0 | @{ $args{fields} } = map { ref $_ ? $_->mutator : $_ } @{ $args{fields} }; | ||||
0 | |||||||
0 | |||||||
0 | |||||||
1429 | |||||||
1430 | # keepextras | ||||||
1431 | 0 | 0 | @{ $args{keepextras} } = map { ref $_ ? $_->mutator : $_ } @{ $args{keepextras} }; | ||||
0 | |||||||
0 | |||||||
0 | |||||||
1432 | |||||||
1433 | |||||||
1434 | # values | ||||||
1435 | 0 | 0 | @{ $args{values} } = map { defined $_ ? ''.$_ : undef } @{ $args{values} }; | ||||
0 | |||||||
0 | |||||||
0 | |||||||
1436 | |||||||
1437 | |||||||
1438 | # validate | ||||||
1439 | |||||||
1440 | |||||||
1441 | # auto_validate is still in here - needs to be removed | ||||||
1442 | |||||||
1443 | |||||||
1444 | #warn "Clean args: " . Dumper( \%args ); | ||||||
1445 | 0 | return %args; | |||||
1446 | } | ||||||
1447 | |||||||
1448 | =item as_form_with_related | ||||||
1449 | |||||||
1450 | B |
||||||
1451 | |||||||
1452 | B |
||||||
1453 | with this code, when it's working properly. | ||||||
1454 | |||||||
1455 | Builds a form with fields from the target CDBI class/object, plus fields from the related objects. | ||||||
1456 | |||||||
1457 | Accepts the same arguments as C |
||||||
1458 | |||||||
1459 | =over 4 | ||||||
1460 | |||||||
1461 | =item related | ||||||
1462 | |||||||
1463 | A hashref of C<< $field_name => $as_form_args_hashref >> settings. Each C<$as_form_args_hashref> | ||||||
1464 | can take all the same settings as C |
||||||
1465 | object(s) referred to by that field. For instance, you could use this to only display a subset of the | ||||||
1466 | fields of the related class. | ||||||
1467 | |||||||
1468 | =item show_related | ||||||
1469 | |||||||
1470 | By default, all related fields are shown in the form. To only expand selected related fields, list | ||||||
1471 | them in C |
||||||
1472 | |||||||
1473 | =back | ||||||
1474 | |||||||
1475 | =cut | ||||||
1476 | |||||||
1477 | sub as_form_with_related | ||||||
1478 | { | ||||||
1479 | 0 | 0 | 1 | my ( $proto, %args ) = @_; | |||
1480 | |||||||
1481 | 0 | my $cdbifb = $proto->__form_builder_subclass__; | |||||
1482 | |||||||
1483 | 0 | my $related_args = delete( $args{related} ); | |||||
1484 | 0 | 0 | my $show_related = delete( $args{show_related} ) || []; | ||||
1485 | |||||||
1486 | 0 | my $parent_form = $proto->as_form( %args ); | |||||
1487 | |||||||
1488 | 0 | foreach my $field ( $cdbifb->_fields_and_has_many_accessors( $proto, $parent_form, $show_related ) ) | |||||
1489 | { | ||||||
1490 | # object or class | ||||||
1491 | 0 | my ( $related, $rel_type ) = $cdbifb->_related( $proto, $field ); | |||||
1492 | |||||||
1493 | 0 | 0 | next unless $related; | ||||
1494 | |||||||
1495 | 0 | 0 | my @relateds = ref( $related ) eq 'ARRAY' ? @$related : ( $related ); | ||||
1496 | |||||||
1497 | 0 | $cdbifb->_splice_form( $_, $parent_form, $field, $related_args->{ $field }, $rel_type ) for @relateds; | |||||
1498 | } | ||||||
1499 | |||||||
1500 | 0 | return $parent_form; | |||||
1501 | } | ||||||
1502 | |||||||
1503 | # deliberately ugly name to encourage something more generic in future | ||||||
1504 | sub _fields_and_has_many_accessors | ||||||
1505 | { | ||||||
1506 | 0 | 0 | my ( $me, $them, $form, $show_related ) = @_; | ||||
1507 | |||||||
1508 | 0 | 0 | return @$show_related if @$show_related; | ||||
1509 | |||||||
1510 | # Cleaning these out appears not to fix multiple pc fields, but also seems like the | ||||||
1511 | # right thing to do. | ||||||
1512 | 0 | my %pc = map { $_ => 1 } $them->primary_columns; | |||||
0 | |||||||
1513 | |||||||
1514 | 0 | my @fields = grep { ! $pc{ $_ } } $form->field; | |||||
0 | |||||||
1515 | |||||||
1516 | 0 | my %seen = map { $_ => 1 } @fields; | |||||
0 | |||||||
1517 | |||||||
1518 | 0 | 0 | my @related = keys %{ $them->meta_info( 'has_many' ) || {} }; | ||||
0 | |||||||
1519 | |||||||
1520 | 0 | push @fields, grep { ! $seen{ $_ } } @related; | |||||
0 | |||||||
1521 | |||||||
1522 | 0 | return @fields; | |||||
1523 | } | ||||||
1524 | |||||||
1525 | # Add fields representing related class/object $them, to $parent_form, which represents | ||||||
1526 | # the class/object as_form_with_related was called on. E.g. add brewery, style, and many pubs | ||||||
1527 | # to a beer form. | ||||||
1528 | sub _splice_form | ||||||
1529 | { | ||||||
1530 | 0 | 0 | my ( $me, $them, $parent_form, $field_name, $args, $rel_type ) = @_; | ||||
1531 | |||||||
1532 | # related pkdata are encoded in the fake field name | ||||||
1533 | 0 | warn 'not sure if pk for related objects is getting added - if so, it should not'; | |||||
1534 | |||||||
1535 | #warn "need to add 'add relatives' button"; - see Maypole edit template now | ||||||
1536 | 0 | 0 | return unless ref $them; # for now | ||||
1537 | |||||||
1538 | 0 | my $related_form = $them->as_form( %$args ); | |||||
1539 | |||||||
1540 | 0 | my $moniker = $them->moniker; | |||||
1541 | |||||||
1542 | 0 | my @related_fields; | |||||
1543 | |||||||
1544 | 0 | foreach my $related_field ( $related_form->fields ) | |||||
1545 | { | ||||||
1546 | 0 | my $related_field_name = $related_field->name; # XXX mutator | |||||
1547 | |||||||
1548 | 0 | my $fake_name = $me->_false_related_field_name( $them, $related_field_name ); | |||||
1549 | |||||||
1550 | 0 | $related_field->_form( $parent_form ); | |||||
1551 | |||||||
1552 | 0 | $related_field->name( $fake_name ); | |||||
1553 | |||||||
1554 | 0 | 0 | $related_field->label( ucfirst( $moniker ) . ': ' . $related_field_name ) | ||||
1555 | unless $args->{labels}{ $related_field_name }; | ||||||
1556 | |||||||
1557 | 0 | $parent_form->{fieldrefs}{ $fake_name } = $related_field; | |||||
1558 | |||||||
1559 | 0 | push @related_fields, $related_field; | |||||
1560 | } | ||||||
1561 | |||||||
1562 | 0 | my $offset = 0; | |||||
1563 | |||||||
1564 | 0 | foreach my $parent_field ( $parent_form->fields ) | |||||
1565 | { | ||||||
1566 | 0 | $offset++; | |||||
1567 | 0 | 0 | last if $parent_field->name eq $field_name; | ||||
1568 | } | ||||||
1569 | |||||||
1570 | 0 | splice @{ $parent_form->{fields} }, $offset, 0, @related_fields; | |||||
0 | |||||||
1571 | |||||||
1572 | # different rel_types get treated differently e.g. is_a should probably not | ||||||
1573 | # allow editing | ||||||
1574 | 0 | 0 | if ( $rel_type eq 'has_a' ) | ||||
0 | |||||||
1575 | { | ||||||
1576 | 0 | $parent_form->field( name => $field_name, | |||||
1577 | type => 'hidden', | ||||||
1578 | ); | ||||||
1579 | } | ||||||
1580 | elsif ( $rel_type eq 'is_a' ) | ||||||
1581 | { | ||||||
1582 | $parent_form->field( name => ''.$_, | ||||||
1583 | readonly => 1, | ||||||
1584 | ) | ||||||
1585 | 0 | for @related_fields; | |||||
1586 | } | ||||||
1587 | |||||||
1588 | } | ||||||
1589 | |||||||
1590 | |||||||
1591 | # ------------------------------------------------------- encode / decode field names ----- | ||||||
1592 | sub _false_related_field_name | ||||||
1593 | { | ||||||
1594 | 0 | 0 | my ( $me, $them, $real_field_name ) = @_; | ||||
1595 | |||||||
1596 | 0 | my $class = $me->_encode_class( $them ); | |||||
1597 | 0 | my $pk = $me->_encode_pk( $them ); | |||||
1598 | |||||||
1599 | 0 | return $real_field_name . $class . $pk; | |||||
1600 | } | ||||||
1601 | |||||||
1602 | sub _real_related_field_name | ||||||
1603 | { | ||||||
1604 | 0 | 0 | my ( $me, $field_name ) = @_; | ||||
1605 | |||||||
1606 | # remove any encoded class | ||||||
1607 | 0 | $field_name =~ s/CDBI_.+_CDBI//; | |||||
1608 | |||||||
1609 | # remove any primary keys | ||||||
1610 | 0 | $field_name =~ s/PKDATA_.+_PKDATA//; | |||||
1611 | |||||||
1612 | 0 | return $field_name; | |||||
1613 | } | ||||||
1614 | |||||||
1615 | sub _encode_pk | ||||||
1616 | { | ||||||
1617 | 0 | 0 | my ( $me, $them ) = @_; | ||||
1618 | |||||||
1619 | 0 | 0 | return '' unless ref( $them ); | ||||
1620 | |||||||
1621 | 0 | my @pk = map { $them->get( $_ ) } $them->primary_columns; | |||||
0 | |||||||
1622 | |||||||
1623 | 0 | die "dots in primary key values will confuse _encode_pk and _decode_pk" | |||||
1624 | 0 | 0 | if grep { /\./ } @pk; | ||||
1625 | |||||||
1626 | 0 | my $pk = sprintf 'PKDATA_%s_PKDATA', join( '.', @pk ); | |||||
1627 | |||||||
1628 | 0 | return $pk; | |||||
1629 | } | ||||||
1630 | |||||||
1631 | sub _decode_pk | ||||||
1632 | { | ||||||
1633 | 0 | 0 | my ( $me, $fake_field_name ) = @_; | ||||
1634 | |||||||
1635 | 0 | 0 | return unless $fake_field_name =~ /PKDATA_(.+)_PKDATA/; | ||||
1636 | |||||||
1637 | 0 | my $pv = $1; | |||||
1638 | |||||||
1639 | 0 | my @pv = split /\./, $pv; | |||||
1640 | |||||||
1641 | 0 | my $class = $me->_decode_class( $fake_field_name ); | |||||
1642 | |||||||
1643 | 0 | my @pc = map { ''.$_ } $class->primary_columns; | |||||
0 | |||||||
1644 | |||||||
1645 | 0 | my %pk = map { $_ => shift( @pv ) } @pc; | |||||
0 | |||||||
1646 | |||||||
1647 | 0 | return %pk; | |||||
1648 | } | ||||||
1649 | |||||||
1650 | sub _decode_class | ||||||
1651 | { | ||||||
1652 | 0 | 0 | my ( $me, $fake_field_name ) = @_; | ||||
1653 | |||||||
1654 | 0 | $fake_field_name =~ /CDBI_(.+)_CDBI/; | |||||
1655 | |||||||
1656 | 0 | my $class = $1; | |||||
1657 | |||||||
1658 | 0 | 0 | $class || die "no class in fake field name $fake_field_name"; | ||||
1659 | |||||||
1660 | 0 | $class =~ s/\./::/g; | |||||
1661 | |||||||
1662 | 0 | return $class; | |||||
1663 | } | ||||||
1664 | |||||||
1665 | sub _encode_class | ||||||
1666 | { | ||||||
1667 | 0 | 0 | my ( $me, $them ) = @_; | ||||
1668 | |||||||
1669 | 0 | 0 | my $token = ref( $them ) || $them; | ||||
1670 | |||||||
1671 | 0 | $token =~ s/::/./g; | |||||
1672 | |||||||
1673 | 0 | return "CDBI_$token\_CDBI"; | |||||
1674 | } | ||||||
1675 | |||||||
1676 | sub _retrieve_entity_from_fake_fname | ||||||
1677 | { | ||||||
1678 | 0 | 0 | my ( $me, $fake_field_name ) = @_; | ||||
1679 | |||||||
1680 | 0 | my $class = $me->_decode_class( $fake_field_name ); | |||||
1681 | |||||||
1682 | 0 | my %pk = $me->_decode_pk( $fake_field_name ); | |||||
1683 | |||||||
1684 | 0 | 0 | return $class unless %pk; | ||||
1685 | |||||||
1686 | 0 | my $obj = $class->retrieve( %pk ); | |||||
1687 | |||||||
1688 | 0 | return $obj; | |||||
1689 | } | ||||||
1690 | |||||||
1691 | # ------------------------------------------------------- end encode / decode field names ----- | ||||||
1692 | |||||||
1693 | =back | ||||||
1694 | |||||||
1695 | =head2 Form modifiers | ||||||
1696 | |||||||
1697 | These methods use CDBI's knowledge about its columns and table relationships to tweak the | ||||||
1698 | form to better represent a CDBI object or class. They can be overridden if you have better | ||||||
1699 | knowledge than CDBI does. For instance, C |
||||||
1700 | select-type columns for MySQL databases. | ||||||
1701 | |||||||
1702 | You can handle new relationship types by subclassing, and writing suitable C |
||||||
1703 | C |
||||||
1704 | |||||||
1705 | C |
||||||
1706 | |||||||
1707 | =over 4 | ||||||
1708 | |||||||
1709 | =item form_hidden | ||||||
1710 | |||||||
1711 | Deprecated. Renamed C |
||||||
1712 | |||||||
1713 | =item form_pks | ||||||
1714 | |||||||
1715 | Ensures primary column fields are included in the form (even if they were not included in the | ||||||
1716 | C |
||||||
1717 | |||||||
1718 | =cut | ||||||
1719 | |||||||
1720 | # these fields are not in the 'fields' list, but are in 'keepextras' | ||||||
1721 | 0 | 0 | 1 | sub form_hidden { warn 'form_hidden is deprecated - use form_pks instead'; goto &form_pks } | |||
0 | |||||||
1722 | |||||||
1723 | sub form_pks | ||||||
1724 | { | ||||||
1725 | 0 | 0 | 1 | my ( $me, $them, $form, $pre_process ) = @_; | |||
1726 | |||||||
1727 | # don't add pk fields to class forms | ||||||
1728 | 0 | 0 | return unless ref $them; | ||||
1729 | |||||||
1730 | 0 | foreach my $field ( $them->primary_columns ) | |||||
1731 | { | ||||||
1732 | 0 | my $process = $me->_add_processors( $field, $pre_process, 'HIDDEN' ); | |||||
1733 | |||||||
1734 | 0 | $me->_process_field( $them, $form, $field, $process ); | |||||
1735 | } | ||||||
1736 | } | ||||||
1737 | |||||||
1738 | =item form_options | ||||||
1739 | |||||||
1740 | Identifies column types that should be represented as select, radiobutton or | ||||||
1741 | checkbox widgets. Currently only works for MySQL C |
||||||
1742 | |||||||
1743 | Patches are welcome for similar column types in other RDBMS's. | ||||||
1744 | |||||||
1745 | Note that you can easily emulate a MySQL C |
||||||
1746 | the validation for the column to an arrayref of values. Emulate a C |
||||||
1747 | setting the C |
||||||
1748 | |||||||
1749 | =cut | ||||||
1750 | |||||||
1751 | sub form_options | ||||||
1752 | { | ||||||
1753 | 0 | 0 | 1 | my ( $me, $them, $form, $pre_process ) = @_; | |||
1754 | |||||||
1755 | 0 | foreach my $field ( $them->columns('All') ) | |||||
1756 | { | ||||||
1757 | 0 | 0 | next unless exists $form->field->{ $field->mutator }; # $form->field( name => $field ); | ||||
1758 | |||||||
1759 | # +OPTIONS_FROM_DB is a no-op if the db column isn't enum or set | ||||||
1760 | 0 | my $process = $me->_add_processors( $field, $pre_process, 'OPTIONS_FROM_DB' ); | |||||
1761 | |||||||
1762 | 0 | $me->_process_field( $them, $form, $field, $process ); | |||||
1763 | } | ||||||
1764 | } | ||||||
1765 | |||||||
1766 | =item form_has_a | ||||||
1767 | |||||||
1768 | Populates a select-type widget with entries representing related objects. Makes the field | ||||||
1769 | required. | ||||||
1770 | |||||||
1771 | Note that this list will be very long if there are lots of rows in the related table. | ||||||
1772 | You may need to override this behaviour by setting up a pre-processor for your C |
||||||
1773 | 'Customising field construction'. | ||||||
1774 | |||||||
1775 | This method assumes the primary key is a single column - patches welcome. | ||||||
1776 | |||||||
1777 | Retrieves every row and creates an object for it - not good for large tables. | ||||||
1778 | |||||||
1779 | If the relationship is to a non-CDBI class, loads a plugin to handle the field (see 'Plugins'). | ||||||
1780 | |||||||
1781 | =cut | ||||||
1782 | |||||||
1783 | sub form_has_a | ||||||
1784 | { | ||||||
1785 | 0 | 0 | 1 | my ($me, $them, $form, $pre_process) = @_; | |||
1786 | |||||||
1787 | 0 | 0 | my $meta = $them->meta_info('has_a') || return; | ||||
1788 | |||||||
1789 | 0 | my @haves = map { $them->find_column($_) } keys %$meta; | |||||
0 | |||||||
1790 | |||||||
1791 | 0 | foreach my $field (@haves) | |||||
1792 | { | ||||||
1793 | 0 | 0 | next unless exists $form->field->{ $field->mutator }; | ||||
1794 | |||||||
1795 | # See Ron's bug report about inconsistent behaviour of processors. | ||||||
1796 | # This will also affect form_has_many and form_might_have | ||||||
1797 | #warn "BUG: it's an error to stop processing the field just because a processor is defined"; | ||||||
1798 | |||||||
1799 | # if a custom field processor has been supplied, use that | ||||||
1800 | 0 | my $processor = $pre_process->{ $field->mutator }; | |||||
1801 | 0 | 0 | $me->_process_field($them, $form, $field, $processor) if $processor; | ||||
1802 | 0 | 0 | next if $processor; | ||||
1803 | |||||||
1804 | 0 | my ($related_class, undef) = $me->table_meta($them)->related_class_and_rel_type($field); | |||||
1805 | |||||||
1806 | 0 | my $nullable = $me->table_meta($them)->column($field)->is_nullable; | |||||
1807 | |||||||
1808 | 0 | 0 | if ( $related_class->isa('Class::DBI') ) | ||||
1809 | { | ||||||
1810 | 0 | 0 | my $options = $me->_field_options($them, $form, $field) || | ||||
1811 | die "No options detected for field '$field'"; | ||||||
1812 | |||||||
1813 | 0 | my ($related_object, $value); | |||||
1814 | |||||||
1815 | 0 | 0 | if (ref $them) | ||||
1816 | { | ||||||
1817 | 0 | my $accessor = $field->accessor; | |||||
1818 | 0 | $related_object = $them->$accessor; | |||||
1819 | |||||||
1820 | 0 | 0 | 0 | if( ! defined $related_object and ! $nullable ) | |||
1821 | { | ||||||
1822 | 0 | die sprintf | |||||
1823 | 'Failed to retrieve a related object from %s has_a field %s - inconsistent db?', | ||||||
1824 | ref $them, $accessor; | ||||||
1825 | } | ||||||
1826 | |||||||
1827 | 0 | 0 | my $pk = $related_object->primary_column if defined $related_object; | ||||
1828 | |||||||
1829 | 0 | 0 | $value = $related_object->$pk if defined $related_object; | ||||
1830 | } | ||||||
1831 | |||||||
1832 | 0 | 0 | my $required = $nullable ? 0 : 1; | ||||
1833 | |||||||
1834 | 0 | $form->field( name => $field->mutator, | |||||
1835 | options => $options, | ||||||
1836 | required => $required, | ||||||
1837 | value => $value, | ||||||
1838 | ); | ||||||
1839 | } | ||||||
1840 | else | ||||||
1841 | { | ||||||
1842 | 0 | my $class = "Class::DBI::FormBuilder::Plugin::$related_class"; | |||||
1843 | |||||||
1844 | # if the class is not in its own file, require will not find it, | ||||||
1845 | # even if it has been loaded | ||||||
1846 | 0 | 0 | 0 | if ( eval { $class->can('field') } or $class->require ) | |||
0 | |||||||
1847 | { | ||||||
1848 | 0 | $class->field($me, $them, $form, $field); | |||||
1849 | } | ||||||
1850 | # elsif ( $@ =~ // ) XXX | ||||||
1851 | # { | ||||||
1852 | # # or simply stringify | ||||||
1853 | # $form->field( name => $field, | ||||||
1854 | # required => 1, | ||||||
1855 | # value => $them->$field.'', | ||||||
1856 | # ); | ||||||
1857 | # } | ||||||
1858 | else | ||||||
1859 | { | ||||||
1860 | 0 | die "Failed to load $class: $@"; | |||||
1861 | } | ||||||
1862 | } | ||||||
1863 | |||||||
1864 | } | ||||||
1865 | } | ||||||
1866 | |||||||
1867 | =item form_has_many | ||||||
1868 | |||||||
1869 | Also assumes a single primary column. | ||||||
1870 | |||||||
1871 | =cut | ||||||
1872 | |||||||
1873 | sub form_has_many | ||||||
1874 | { | ||||||
1875 | 0 | 0 | 1 | my ( $me, $them, $form, $pre_process ) = @_; | |||
1876 | |||||||
1877 | 0 | 0 | my $meta = $them->meta_info( 'has_many' ) || return; | ||||
1878 | |||||||
1879 | 0 | my @has_many_fields = $me->_multiplicity_fields( $them, $form, 'has_many' ); | |||||
1880 | |||||||
1881 | # The target class/object ($them) does not have a column for the related class, | ||||||
1882 | # so we need to add these to the form, then figure out their options. | ||||||
1883 | # Need to make sure and set some attribute to create the new field. | ||||||
1884 | # BUT - do not create the new field if it wasn't in the list passed in the original | ||||||
1885 | # args, or if [] was passed in the original args. | ||||||
1886 | |||||||
1887 | # note that these are *not* columns in $them | ||||||
1888 | 0 | foreach my $field ( @has_many_fields ) | |||||
1889 | { | ||||||
1890 | # the 'next' condition is not tested because @wanted lists fields that probably | ||||||
1891 | # don't exist yet, but should | ||||||
1892 | #next unless exists $form->field->{ $field }; | ||||||
1893 | |||||||
1894 | # if a custom field processor has been supplied, use that | ||||||
1895 | 0 | my $processor = $pre_process->{ $field }; | |||||
1896 | 0 | 0 | $me->_process_field( $them, $form, $field, $processor ) if $processor; | ||||
1897 | 0 | 0 | next if $processor; | ||||
1898 | |||||||
1899 | 0 | 0 | my $options = $me->_field_options( $them, $form, $field ) || | ||||
1900 | die "No options detected for '$them' field '$field'"; | ||||||
1901 | |||||||
1902 | 0 | my @many_pks; | |||||
1903 | |||||||
1904 | 0 | 0 | if ( ref $them ) | ||||
1905 | { | ||||||
1906 | 0 | my $rel = $meta->{ $field }; | |||||
1907 | |||||||
1908 | 0 | 0 | my $accessor = $rel->accessor || die "no accessor for $field"; | ||||
1909 | |||||||
1910 | 0 | my ( $related_class, undef ) = $me->table_meta( $them )->related_class_and_rel_type( $field ); | |||||
1911 | 0 | 0 | die "no foreign_class for $field" unless $related_class; | ||||
1912 | |||||||
1913 | 0 | my $foreign_pk = $related_class->primary_column; | |||||
1914 | |||||||
1915 | # don't be tempted to access pks directly in $iter->data - they may refer to an | ||||||
1916 | # intermediate table via a mapping method | ||||||
1917 | 0 | my $iter = $them->$accessor; | |||||
1918 | |||||||
1919 | 0 | while ( my $obj = $iter->next ) | |||||
1920 | { | ||||||
1921 | 0 | 0 | die "retrieved " . ref( $obj ) . " '$obj' is not a $related_class" | ||||
1922 | unless ref( $obj ) eq $related_class; | ||||||
1923 | |||||||
1924 | 0 | push @many_pks, $obj->$foreign_pk; | |||||
1925 | } | ||||||
1926 | } | ||||||
1927 | |||||||
1928 | 0 | $form->field( name => $field, | |||||
1929 | value => \@many_pks, | ||||||
1930 | options => $options, | ||||||
1931 | multiple => 1, | ||||||
1932 | ); | ||||||
1933 | } | ||||||
1934 | } | ||||||
1935 | |||||||
1936 | =item form_might_have | ||||||
1937 | |||||||
1938 | Also assumes a single primary column. | ||||||
1939 | |||||||
1940 | =cut | ||||||
1941 | |||||||
1942 | # this code is almost identical to form_has_many | ||||||
1943 | sub form_might_have | ||||||
1944 | { | ||||||
1945 | 0 | 0 | 1 | my ( $me, $them, $form, $pre_process ) = @_; | |||
1946 | |||||||
1947 | 0 | 0 | my $meta = $them->meta_info( 'might_have' ) || return; | ||||
1948 | |||||||
1949 | 0 | my @might_have_fields = $me->_multiplicity_fields( $them, $form, 'might_have' ); | |||||
1950 | |||||||
1951 | # note that these are *not* columns in $them | ||||||
1952 | 0 | foreach my $field ( @might_have_fields ) | |||||
1953 | { | ||||||
1954 | # the 'next' condition is not tested because @wanted lists fields that probably | ||||||
1955 | # don't exist yet, but should | ||||||
1956 | |||||||
1957 | # if a custom field processor has been supplied, use that | ||||||
1958 | 0 | my $processor = $pre_process->{ $field }; | |||||
1959 | 0 | 0 | $me->_process_field( $them, $form, $field, $processor ) if $processor; | ||||
1960 | 0 | 0 | next if $processor; | ||||
1961 | |||||||
1962 | 0 | 0 | my $options = $me->_field_options( $them, $form, $field ) || | ||||
1963 | die "No options detected for '$them' field '$field'"; | ||||||
1964 | |||||||
1965 | 0 | my $might_have_object_id; | |||||
1966 | |||||||
1967 | 0 | 0 | if ( ref $them ) | ||||
1968 | { | ||||||
1969 | 0 | my $rel = $meta->{ $field }; | |||||
1970 | |||||||
1971 | 0 | 0 | my $accessor = $rel->accessor || die "no accessor for $field"; | ||||
1972 | |||||||
1973 | 0 | my ( $related_class, undef ) = $me->table_meta( $them )->related_class_and_rel_type( $field ); | |||||
1974 | 0 | 0 | die "no foreign_class for $field" unless $related_class; | ||||
1975 | |||||||
1976 | 0 | my $foreign_pk = $related_class->primary_column; | |||||
1977 | |||||||
1978 | 0 | my $might_have_object = $them->$accessor; | |||||
1979 | |||||||
1980 | 0 | 0 | if ( $might_have_object ) | ||||
1981 | { | ||||||
1982 | 0 | 0 | die "retrieved " . ref( $might_have_object ) . " '$might_have_object' is not a $related_class" | ||||
1983 | unless ref( $might_have_object ) eq $related_class; | ||||||
1984 | } | ||||||
1985 | |||||||
1986 | 0 | 0 | $might_have_object_id = $might_have_object ? $might_have_object->$foreign_pk : undef; # was '' | ||||
1987 | } | ||||||
1988 | |||||||
1989 | 0 | $form->field( name => $field, | |||||
1990 | value => $might_have_object_id, | ||||||
1991 | options => $options, | ||||||
1992 | ); | ||||||
1993 | } | ||||||
1994 | } | ||||||
1995 | |||||||
1996 | # Returns fields (in random order) that represent has_many or might_have relationships. | ||||||
1997 | # Note that if any of these fields are specified in __cdbi_original_args__, the order will be | ||||||
1998 | # preserved elsewhere during form construction. | ||||||
1999 | sub _multiplicity_fields | ||||||
2000 | { | ||||||
2001 | 0 | 0 | my ( $me, $them, $form, $rel ) = @_; | ||||
2002 | |||||||
2003 | 0 | 0 | die "Can't handle $rel relationships yet" unless $rel =~ /^(?:has_many|might_have)$/; | ||||
2004 | |||||||
2005 | 0 | 0 | my $meta = $them->meta_info( $rel ) || return; | ||||
2006 | |||||||
2007 | # @extras are field names that do not exist as columns in the db | ||||||
2008 | 0 | my @extras = keys %$meta; | |||||
2009 | |||||||
2010 | # if the call to as_form explicitly specified a list of fields, we only return | ||||||
2011 | # fields from @extras that are in that list | ||||||
2012 | 0 | 0 | my %allowed = map { $_ => 1 } @{ $form->{__cdbi_original_args__}->{fields} || [ @extras ] }; | ||||
0 | |||||||
0 | |||||||
2013 | |||||||
2014 | 0 | my @wanted = grep { $allowed{ $_ } } @extras; | |||||
0 | |||||||
2015 | |||||||
2016 | 0 | return @wanted; | |||||
2017 | } | ||||||
2018 | |||||||
2019 | # $field can be a CDBI column object, or the name of a has_many etc. field - i.e. not a column | ||||||
2020 | # in $them, but in another class | ||||||
2021 | sub _field_options | ||||||
2022 | { | ||||||
2023 | 0 | 0 | my ( $me, $them, $form, $field ) = @_; | ||||
2024 | |||||||
2025 | 0 | my ($related_class, undef) = $me->table_meta($them)->related_class_and_rel_type($field); | |||||
2026 | |||||||
2027 | 0 | 0 | return unless $related_class; | ||||
2028 | |||||||
2029 | 0 | 0 | return unless $related_class->isa( 'Class::DBI' ); | ||||
2030 | |||||||
2031 | 0 | my $iter = $related_class->retrieve_all; # potentially expensive | |||||
2032 | |||||||
2033 | 0 | my $pk = $related_class->primary_column; | |||||
2034 | |||||||
2035 | 0 | my @options; | |||||
2036 | |||||||
2037 | 0 | my $column_meta = $me->table_meta($them)->column($field); | |||||
2038 | 0 | 0 | 0 | push @options, [ undef, 'n/a' ] if $column_meta and $column_meta->is_nullable; | |||
2039 | |||||||
2040 | 0 | while ( my $object = $iter->next ) # potentially very expensive | |||||
2041 | { | ||||||
2042 | 0 | push @options, [ $object->$pk, ''.$object ]; | |||||
2043 | } | ||||||
2044 | |||||||
2045 | 0 | 0 | if ( my $sorter = $me->_get_options_sorter( $them, $form, $field ) ) | ||||
2046 | { | ||||||
2047 | 0 | @options = sort $sorter @options; | |||||
2048 | } | ||||||
2049 | |||||||
2050 | 0 | return \@options; | |||||
2051 | } | ||||||
2052 | |||||||
2053 | sub _get_options_sorter | ||||||
2054 | { | ||||||
2055 | 0 | 0 | my ( $me, $them, $form, $field ) = @_; | ||||
2056 | |||||||
2057 | # this href is a merge between the original args, and form_builder_defaults | ||||||
2058 | 0 | my $sorter = $form->__cdbi_original_args__->{options_sorters}->{$field}; | |||||
2059 | |||||||
2060 | 0 | return $sorter; | |||||
2061 | } | ||||||
2062 | |||||||
2063 | =item form_timestamp | ||||||
2064 | |||||||
2065 | Makes timestamp columns read only, since they will be set by the database. | ||||||
2066 | |||||||
2067 | The default is to use the C |
||||||
2068 | processor, which sets the HTML C |
||||||
2069 | |||||||
2070 | If you prefer, you can replace the C |
||||||
2071 | |||||||
2072 | =cut | ||||||
2073 | |||||||
2074 | sub form_timestamp | ||||||
2075 | { | ||||||
2076 | 0 | 0 | 1 | my ($me, $them, $form, $pre_process) = @_; | |||
2077 | |||||||
2078 | 0 | foreach my $field ( $them->columns('All') ) | |||||
2079 | { | ||||||
2080 | 0 | 0 | next unless exists $form->field->{ $field->mutator }; | ||||
2081 | |||||||
2082 | 0 | 0 | next unless $me->table_meta($them)->column_deep_type( $field->name ) eq 'timestamp'; | ||||
2083 | |||||||
2084 | 0 | my $process = $me->_add_processors($field, $pre_process, 'TIMESTAMP'); | |||||
2085 | |||||||
2086 | 0 | $me->_process_field($them, $form, $field, $process); | |||||
2087 | } | ||||||
2088 | } | ||||||
2089 | |||||||
2090 | =item form_text | ||||||
2091 | |||||||
2092 | Makes C |
||||||
2093 | |||||||
2094 | =cut | ||||||
2095 | |||||||
2096 | sub form_text | ||||||
2097 | { | ||||||
2098 | 0 | 0 | 1 | my ( $me, $them, $form, $pre_process ) = @_; | |||
2099 | |||||||
2100 | 0 | foreach my $field ( $them->columns( 'All' ) ) | |||||
2101 | { | ||||||
2102 | 0 | 0 | next unless exists $form->field->{ $field->mutator }; | ||||
2103 | |||||||
2104 | 0 | 0 | next unless $me->table_meta( $them )->column_deep_type( $field->name ) eq 'text'; | ||||
2105 | |||||||
2106 | 0 | my $process = $me->_add_processors( $field, $pre_process, [ '+SET_type(textarea)', '+VALUE' ] ); | |||||
2107 | |||||||
2108 | 0 | $me->_process_field( $them, $form, $field, $process ); | |||||
2109 | } | ||||||
2110 | } | ||||||
2111 | |||||||
2112 | =item form_file | ||||||
2113 | |||||||
2114 | B |
||||||
2115 | in the C |
||||||
2116 | |||||||
2117 | Figures out if a column contains file data. | ||||||
2118 | |||||||
2119 | If somebody can show me how to automatically detect that a column stores binary data, then this method | ||||||
2120 | could actually do something useful. | ||||||
2121 | |||||||
2122 | If you are in the habit of using a naming convention that allows you to identify C |
||||||
2123 | you could subclass L |
||||||
2124 | |||||||
2125 | # use a naming convention to configure file columns | ||||||
2126 | sub form_file | ||||||
2127 | { | ||||||
2128 | my ( $me, $them, $form, $pre_process ) = @_; | ||||||
2129 | |||||||
2130 | foreach my $field ( $them->columns( 'All' ) ) | ||||||
2131 | { | ||||||
2132 | next unless $field->name =~ /^file_\w+$/; | ||||||
2133 | |||||||
2134 | my $process = $me->_add_processors( $field, $pre_process, 'FILE' ); | ||||||
2135 | |||||||
2136 | $me->_process_field( $them, $form, $field, $process ); | ||||||
2137 | } | ||||||
2138 | } | ||||||
2139 | |||||||
2140 | =cut | ||||||
2141 | |||||||
2142 | sub form_file | ||||||
2143 | { | ||||||
2144 | 0 | 0 | 1 | my ( $me, $them, $form, $pre_process ) = @_; | |||
2145 | |||||||
2146 | 0 | return; | |||||
2147 | } | ||||||
2148 | |||||||
2149 | =item form_process_extras | ||||||
2150 | |||||||
2151 | This processor adds any fields in the C |
||||||
2152 | This is a useful method for adding custom fields (i.e. fields that do not represent anything about | ||||||
2153 | the CDBI object) to a form. | ||||||
2154 | |||||||
2155 | You can skip this stage by setting C<< process_fields->{__SKIP_PROCESS_EXTRAS__} >> to a true | ||||||
2156 | value. For instance, in C |
||||||
2157 | already present in C |
||||||
2158 | from being added to the button form. | ||||||
2159 | |||||||
2160 | =cut | ||||||
2161 | |||||||
2162 | sub form_process_extras | ||||||
2163 | { | ||||||
2164 | 0 | 0 | 1 | my ( $me, $them, $form, $pre_process ) = @_; | |||
2165 | |||||||
2166 | # this is a flag used in Maypole::FormBuilder::Model::setup_form_mode() button modes to | ||||||
2167 | # prevent extra fields that may be mentioned in form_builder_defaults->{process_fields} | ||||||
2168 | # from being added to the form | ||||||
2169 | #return if $pre_process->{__SKIP_PROCESS_EXTRAS__}; | ||||||
2170 | |||||||
2171 | 0 | my %process_extras = map { $_ => 1 } @{ $form->__cdbi_original_args__->{process_extras} }; | |||||
0 | |||||||
0 | |||||||
2172 | |||||||
2173 | 0 | 0 | return unless %process_extras; | ||||
2174 | |||||||
2175 | 0 | foreach my $field ( keys %$pre_process ) | |||||
2176 | { | ||||||
2177 | 0 | 0 | next if exists $form->field->{ $field }; | ||||
2178 | |||||||
2179 | #next if $field eq '__FINAL__'; # reserved for form_final | ||||||
2180 | |||||||
2181 | 0 | 0 | next unless $process_extras{ $field }; | ||||
2182 | |||||||
2183 | #my $process = $pre_process->{ $field }; | ||||||
2184 | # this is just to help with debugging _add_processors | ||||||
2185 | 0 | my $process = $me->_add_processors( $field, $pre_process, [ ] ); | |||||
2186 | |||||||
2187 | 0 | $me->_process_field( $them, $form, $field, $process ); | |||||
2188 | } | ||||||
2189 | } | ||||||
2190 | |||||||
2191 | =item form_final | ||||||
2192 | |||||||
2193 | After running all previous field processors (including C |
||||||
2194 | chance to run code to modify all fields in the completed form. Use this by setting a field | ||||||
2195 | processor in the special C<__FINAL__> slot of C |
||||||
2196 | |||||||
2197 | And avoid naming any of your normal columns or fields C<__FINAL__>. | ||||||
2198 | |||||||
2199 | =cut | ||||||
2200 | |||||||
2201 | sub form_final | ||||||
2202 | { | ||||||
2203 | 0 | 0 | 1 | my ( $me, $them, $form, $pre_process ) = @_; | |||
2204 | |||||||
2205 | 0 | 0 | my $final = $pre_process->{__FINAL__} or return; | ||||
2206 | |||||||
2207 | 0 | $me->_process_field( $them, $form, $_, $final ) for map { $_->name } $form->fields; | |||||
0 | |||||||
2208 | } | ||||||
2209 | |||||||
2210 | =back | ||||||
2211 | |||||||
2212 | =head2 Form handling methods | ||||||
2213 | |||||||
2214 | All these methods check the form like this | ||||||
2215 | |||||||
2216 | return unless $fb->submitted && $fb->validate; | ||||||
2217 | |||||||
2218 | which allows you to say things like | ||||||
2219 | |||||||
2220 | print Film->update_from_form( $form ) ? $form->confirm : $form->render; | ||||||
2221 | |||||||
2222 | That's pretty concise! | ||||||
2223 | |||||||
2224 | =over 4 | ||||||
2225 | |||||||
2226 | =item create_from_form( $form ) | ||||||
2227 | |||||||
2228 | Creates and returns a new object. | ||||||
2229 | |||||||
2230 | =cut | ||||||
2231 | |||||||
2232 | sub create_from_form | ||||||
2233 | { | ||||||
2234 | 0 | 0 | 1 | my ( $them, $form ) = @_; | |||
2235 | |||||||
2236 | 0 | 0 | Carp::croak "create_from_form can only be called as a class method" if ref $them; | ||||
2237 | |||||||
2238 | 0 | 0 | 0 | return unless $form->submitted && $form->validate; | |||
2239 | |||||||
2240 | 0 | my $me = $them->__form_builder_subclass__; | |||||
2241 | |||||||
2242 | 0 | my $created = $them->create( $me->_fb_create_data( $them, $form ) ); | |||||
2243 | |||||||
2244 | 0 | $me->_update_many_to_many( $created, $form ); | |||||
2245 | |||||||
2246 | 0 | return $created; | |||||
2247 | } | ||||||
2248 | |||||||
2249 | sub _fb_create_data | ||||||
2250 | { | ||||||
2251 | 0 | 0 | my ( $me, $them, $form ) = @_; | ||||
2252 | |||||||
2253 | 0 | my $cols = {}; | |||||
2254 | |||||||
2255 | 0 | my $data = $form->fields; | |||||
2256 | |||||||
2257 | 0 | foreach my $column ( $them->columns('All') ) | |||||
2258 | { | ||||||
2259 | 0 | 0 | next unless exists $data->{ $column->name }; | ||||
2260 | |||||||
2261 | 0 | $cols->{ $column->mutator } = $data->{ $column->name }; | |||||
2262 | } | ||||||
2263 | |||||||
2264 | 0 | return $cols; | |||||
2265 | } | ||||||
2266 | |||||||
2267 | =item create_from_multiform | ||||||
2268 | |||||||
2269 | Creates multiple new objects from a C |
||||||
2270 | |||||||
2271 | =cut | ||||||
2272 | |||||||
2273 | # TODO: check if we need to call _update_many_many | ||||||
2274 | sub create_from_multiform | ||||||
2275 | { | ||||||
2276 | 0 | 0 | 1 | my ( $them, $form ) = @_; | |||
2277 | |||||||
2278 | 0 | 0 | Carp::croak "create_from_multiform can only be called as a class method" if ref $them; | ||||
2279 | |||||||
2280 | 0 | 0 | 0 | return unless $form->submitted && $form->validate; | |||
2281 | |||||||
2282 | 0 | my $form_data = $form->field; | |||||
2283 | |||||||
2284 | 0 | my $items_data; | |||||
2285 | |||||||
2286 | 0 | foreach my $fname ( keys %$form_data ) | |||||
2287 | { | ||||||
2288 | 0 | $fname =~ /^R(\d+)__(\w+)$/; | |||||
2289 | |||||||
2290 | 0 | my $item_num = $1; | |||||
2291 | 0 | my $col_name = $2; | |||||
2292 | |||||||
2293 | 0 | my $mutator = $them->find_column( $col_name )->mutator; | |||||
2294 | |||||||
2295 | 0 | $items_data->{ $item_num }->{ $mutator } = $form_data->{ $fname }; | |||||
2296 | } | ||||||
2297 | |||||||
2298 | 0 | my @new = map { $them->create( $_ ) } values %$items_data; | |||||
0 | |||||||
2299 | |||||||
2300 | 0 | return @new; | |||||
2301 | } | ||||||
2302 | |||||||
2303 | =item update_from_form( $form ) | ||||||
2304 | |||||||
2305 | Updates an existing CDBI object. | ||||||
2306 | |||||||
2307 | If called on an object, will update that object. | ||||||
2308 | |||||||
2309 | If called on a class, will first retrieve the relevant object (via C |
||||||
2310 | |||||||
2311 | =cut | ||||||
2312 | |||||||
2313 | sub update_from_form | ||||||
2314 | { | ||||||
2315 | 0 | 0 | 1 | my ( $proto, $form ) = @_; | |||
2316 | |||||||
2317 | 0 | 0 | my $them = ref( $proto ) ? $proto : $proto->retrieve_from_form( $form ); | ||||
2318 | |||||||
2319 | 0 | 0 | Carp::croak "No object found matching submitted primary key data" unless $them; | ||||
2320 | |||||||
2321 | 0 | my $me = $proto->__form_builder_subclass__; | |||||
2322 | |||||||
2323 | 0 | $me->_run_update( $them, $form ); | |||||
2324 | |||||||
2325 | 0 | $me->_update_many_to_many( $them, $form ); | |||||
2326 | |||||||
2327 | 0 | return $them; | |||||
2328 | } | ||||||
2329 | |||||||
2330 | sub _run_update | ||||||
2331 | { | ||||||
2332 | 0 | 0 | my ( $me, $them, $fb ) = @_; | ||||
2333 | |||||||
2334 | 0 | 0 | 0 | return unless $fb->submitted && $fb->validate; | |||
2335 | |||||||
2336 | 0 | my $formdata = $fb->fields; | |||||
2337 | |||||||
2338 | # I think this is now unnecessary (0.4), because pks are in keepextras | ||||||
2339 | 0 | delete $formdata->{ $_ } for map {''.$_} $them->primary_columns; | |||||
0 | |||||||
2340 | |||||||
2341 | # assumes no extra fields in the form | ||||||
2342 | #$them->set( %$formdata ); | ||||||
2343 | |||||||
2344 | # Start with all possible columns. Only ask for the subset represented | ||||||
2345 | # in the form. This allows correct handling of fields that result in | ||||||
2346 | # 'missing' entries in the submitted data - e.g. checkbox groups with | ||||||
2347 | # no item selected will not even appear in the raw request data, but here | ||||||
2348 | # they should result in an undef value being sent to the object. | ||||||
2349 | 0 | my %coldata = map { $_->mutator => $formdata->{ $_->name } } | |||||
0 | |||||||
2350 | 0 | grep { exists $formdata->{ $_->name } } | |||||
2351 | $them->columns( 'All' ); | ||||||
2352 | |||||||
2353 | 0 | $them->set( %coldata ); | |||||
2354 | |||||||
2355 | 0 | $them->update; | |||||
2356 | |||||||
2357 | 0 | return $them; | |||||
2358 | } | ||||||
2359 | |||||||
2360 | |||||||
2361 | # from Ron McClain: | ||||||
2362 | sub _update_many_to_many | ||||||
2363 | { | ||||||
2364 | 0 | 0 | my ( $me, $obj, $form ) = @_; | ||||
2365 | |||||||
2366 | 0 | 0 | my $has_many = $obj->meta_info('has_many') || return; | ||||
2367 | |||||||
2368 | 0 | foreach my $field ( keys %{ $form->fields } ) | |||||
0 | |||||||
2369 | { | ||||||
2370 | 0 | 0 | next unless $has_many->{$field}; | ||||
2371 | |||||||
2372 | # many-many | ||||||
2373 | 0 | 0 | next unless $has_many->{$field}->{args}->{mapping}; | ||||
2374 | |||||||
2375 | 0 | my $mkey = $has_many->{$field}->{args}->{mapping}->[0]; | |||||
2376 | 0 | my $fkey = $has_many->{$field}->{args}->{foreign_key}; | |||||
2377 | 0 | my $fclass = $has_many->{$field}->{foreign_class}; | |||||
2378 | |||||||
2379 | 0 | my %rel_exists; | |||||
2380 | |||||||
2381 | 0 | foreach my $rel ( $fclass->search( $fkey => $obj->id ) ) | |||||
2382 | { | ||||||
2383 | 0 | 0 | if ( grep { $rel->$mkey->id == $_ } $form->field($field) ) | ||||
0 | |||||||
2384 | { | ||||||
2385 | 0 | $rel_exists{ $rel->$mkey->id }++; | |||||
2386 | } | ||||||
2387 | else | ||||||
2388 | { | ||||||
2389 | 0 | $rel->delete; | |||||
2390 | } | ||||||
2391 | } | ||||||
2392 | |||||||
2393 | 0 | foreach my $val ( $form->field($field) ) | |||||
2394 | { | ||||||
2395 | 0 | 0 | $fclass->create( { $fkey => $obj->id, | ||||
2396 | $mkey => $val, | ||||||
2397 | } ) | ||||||
2398 | unless $rel_exists{$val}; | ||||||
2399 | } | ||||||
2400 | } | ||||||
2401 | } | ||||||
2402 | |||||||
2403 | # Also, this patch only applies to many-many. Not one-many. I got to | ||||||
2404 | # thinking about it, and it doesn't make sense to me have a select list | ||||||
2405 | # for one-many with existing records.. Because if you edit a record and | ||||||
2406 | # select a record to relate to it.. The related record may already be | ||||||
2407 | # associated with a separate record, and it would kill that association.. | ||||||
2408 | # Not intuitive. But for many-many, I don't see the downside of having | ||||||
2409 | # something like this be standard. The only thing I can think of is, what | ||||||
2410 | # if the glue table has additional columns besides the two foreign keys? | ||||||
2411 | # I can't think of an example right now, but I guess it's possible. The | ||||||
2412 | # other thing I don't quite understand is why | ||||||
2413 | # meta_info->field->args->mapping is an array and not a scalar. I just | ||||||
2414 | # pull off the first element, but I don't know whether it's possible that | ||||||
2415 | # there be more elements than that, and what they mean. | ||||||
2416 | |||||||
2417 | =item update_or_create_from_form | ||||||
2418 | |||||||
2419 | Class method. | ||||||
2420 | |||||||
2421 | Attempts to look up an object (using primary key data submitted in the form) and update it. | ||||||
2422 | |||||||
2423 | If none exists (or if no values for primary keys are supplied), a new object is created. | ||||||
2424 | |||||||
2425 | =cut | ||||||
2426 | |||||||
2427 | sub update_or_create_from_form | ||||||
2428 | { | ||||||
2429 | 0 | 0 | 1 | my ( $them, $form ) = @_; | |||
2430 | |||||||
2431 | 0 | 0 | Carp::croak "update_or_create_from_form can only be called as a class method" if ref $them; | ||||
2432 | |||||||
2433 | 0 | $them->__form_builder_subclass__->_run_update_or_create_from_form( $them, $form ); | |||||
2434 | } | ||||||
2435 | |||||||
2436 | sub _run_update_or_create_from_form | ||||||
2437 | { | ||||||
2438 | 0 | 0 | my ( $me, $them, $form ) = @_; | ||||
2439 | |||||||
2440 | 0 | 0 | 0 | return unless $form->submitted && $form->validate; | |||
2441 | |||||||
2442 | 0 | my $object = $them->retrieve_from_form( $form ); | |||||
2443 | |||||||
2444 | 0 | 0 | return $object->update_from_form( $form ) if $object; | ||||
2445 | |||||||
2446 | 0 | $them->create_from_form( $form ); | |||||
2447 | } | ||||||
2448 | |||||||
2449 | =back | ||||||
2450 | |||||||
2451 | =head2 Search methods | ||||||
2452 | |||||||
2453 | Note that search methods (except for C |
||||||
2454 | in scalar context, and a (possibly empty) list of objects in list context. | ||||||
2455 | |||||||
2456 | All the search methods except C |
||||||
2457 | built using C |
||||||
2458 | because of missing required fields specified by C |
||||||
2459 | configure any fields as required). | ||||||
2460 | |||||||
2461 | =over 4 | ||||||
2462 | |||||||
2463 | =item retrieve_from_form | ||||||
2464 | |||||||
2465 | Use primary key data in a form to retrieve a single object. | ||||||
2466 | |||||||
2467 | =cut | ||||||
2468 | |||||||
2469 | sub retrieve_from_form | ||||||
2470 | { | ||||||
2471 | 0 | 0 | 1 | my ( $them, $form ) = @_; | |||
2472 | |||||||
2473 | 0 | 0 | Carp::croak "retrieve_from_form can only be called as a class method" if ref $them; | ||||
2474 | |||||||
2475 | 0 | $them->__form_builder_subclass__->_run_retrieve_from_form( $them, $form ); | |||||
2476 | } | ||||||
2477 | |||||||
2478 | sub _run_retrieve_from_form | ||||||
2479 | { | ||||||
2480 | 0 | 0 | my ( $me, $them, $form ) = @_; | ||||
2481 | |||||||
2482 | # we don't validate because pk data must side-step validation as it's | ||||||
2483 | # unknowable in advance whether they will even be present. | ||||||
2484 | #return unless $fb->submitted && $fb->validate; | ||||||
2485 | |||||||
2486 | 0 | 0 | my %pkdata = map { $_ => $form->cgi_param( $_->mutator ) || undef } $them->primary_columns; | ||||
0 | |||||||
2487 | |||||||
2488 | 0 | return $them->retrieve( %pkdata ); | |||||
2489 | } | ||||||
2490 | |||||||
2491 | =item search_from_form | ||||||
2492 | |||||||
2493 | Lookup by column values. | ||||||
2494 | |||||||
2495 | =cut | ||||||
2496 | |||||||
2497 | sub search_from_form | ||||||
2498 | { | ||||||
2499 | 0 | 0 | 1 | my ( $them, $form ) = @_; | |||
2500 | |||||||
2501 | 0 | 0 | Carp::croak "search_from_form can only be called as a class method" if ref $them; | ||||
2502 | |||||||
2503 | 0 | $them->__form_builder_subclass__->_run_search_from_form( $them, '=', $form ); | |||||
2504 | } | ||||||
2505 | |||||||
2506 | =item search_like_from_form | ||||||
2507 | |||||||
2508 | Allows wildcard searches (% or _). | ||||||
2509 | |||||||
2510 | Note that the submitted form should be built using C |
||||||
2511 | |||||||
2512 | =cut | ||||||
2513 | |||||||
2514 | sub search_like_from_form | ||||||
2515 | { | ||||||
2516 | 0 | 0 | 1 | my ( $them, $form ) = @_; | |||
2517 | |||||||
2518 | 0 | 0 | Carp::croak "search_like_from_form can only be called as a class method" if ref $them; | ||||
2519 | |||||||
2520 | 0 | $them->__form_builder_subclass__->_run_search_from_form( $them, 'LIKE', $form ); | |||||
2521 | } | ||||||
2522 | |||||||
2523 | sub _run_search_from_form | ||||||
2524 | { | ||||||
2525 | 0 | 0 | my ( $me, $them, $search_type, $form ) = @_; | ||||
2526 | |||||||
2527 | 0 | 0 | 0 | return unless $form->submitted && $form->validate; | |||
2528 | |||||||
2529 | 0 | my %searches = ( LIKE => 'search_like', | |||||
2530 | '=' => 'search', | ||||||
2531 | ); | ||||||
2532 | |||||||
2533 | 0 | my $search_method = $searches{ $search_type }; | |||||
2534 | |||||||
2535 | 0 | my @search = $me->_get_search_spec( $them, $form ); | |||||
2536 | |||||||
2537 | 0 | my @modifiers = qw( order_by order_direction ); # others too | |||||
2538 | |||||||
2539 | 0 | my %search_modifiers = $me->_get_search_spec( $them, $form, \@modifiers ); | |||||
2540 | |||||||
2541 | 0 | 0 | push( @search, \%search_modifiers ) if %search_modifiers; | ||||
2542 | |||||||
2543 | 0 | return $them->$search_method( @search ); | |||||
2544 | } | ||||||
2545 | |||||||
2546 | sub _get_search_spec | ||||||
2547 | { | ||||||
2548 | 0 | 0 | my ( $me, $them, $form, $fields ) = @_; | ||||
2549 | |||||||
2550 | 0 | 0 | my @fields = $fields ? @$fields : map { $_->accessor } $them->columns( 'All' ); | ||||
0 | |||||||
2551 | |||||||
2552 | # this would miss multiple items | ||||||
2553 | #my $formdata = $fb->fields; | ||||||
2554 | |||||||
2555 | 0 | my $formdata; | |||||
2556 | |||||||
2557 | 0 | foreach my $field ( $form->fields ) | |||||
2558 | { | ||||||
2559 | 0 | my @data = $field->value; | |||||
2560 | |||||||
2561 | 0 | 0 | $formdata->{ $field } = @data > 1 ? \@data : $data[0]; | ||||
2562 | } | ||||||
2563 | |||||||
2564 | 0 | return map { $_ => $formdata->{ $_ } } | |||||
0 | |||||||
2565 | 0 | grep { defined $formdata->{ $_ } } # don't search on unsubmitted fields | |||||
2566 | @fields; | ||||||
2567 | } | ||||||
2568 | |||||||
2569 | =item search_where_from_form | ||||||
2570 | |||||||
2571 | L |
||||||
2572 | CDBI class for this to work. | ||||||
2573 | |||||||
2574 | If no search terms are specified, then the search | ||||||
2575 | |||||||
2576 | WHERE 1 = 1 | ||||||
2577 | |||||||
2578 | is executed (returns all rows), no matter what search operator may have been selected. | ||||||
2579 | |||||||
2580 | =cut | ||||||
2581 | |||||||
2582 | sub search_where_from_form | ||||||
2583 | { | ||||||
2584 | 0 | 0 | 1 | my ( $them, $form ) = @_; | |||
2585 | |||||||
2586 | 0 | 0 | Carp::croak "search_where_from_form can only be called as a class method" if ref $them; | ||||
2587 | |||||||
2588 | 0 | $them->__form_builder_subclass__->_run_search_where_from_form( $them, $form ); | |||||
2589 | } | ||||||
2590 | |||||||
2591 | # have a look at Maypole::Model::CDBI::search() | ||||||
2592 | sub _run_search_where_from_form | ||||||
2593 | { | ||||||
2594 | 0 | 0 | my ( $me, $them, $form ) = @_; | ||||
2595 | |||||||
2596 | 0 | 0 | 0 | return unless $form->submitted && $form->validate; | |||
2597 | |||||||
2598 | 0 | my %search_data = $me->_get_search_spec( $them, $form ); | |||||
2599 | |||||||
2600 | # clean out empty fields | ||||||
2601 | 0 | 0 | do { delete( $search_data{ $_ } ) unless $search_data{ $_ } } for keys %search_data; | ||||
0 | |||||||
2602 | |||||||
2603 | # these match fields added in search_form() | ||||||
2604 | 0 | my %modifiers = ( search_opt_cmp => 'cmp', | |||||
2605 | search_opt_order_by => 'order_by', | ||||||
2606 | ); | ||||||
2607 | |||||||
2608 | 0 | my %search_modifiers = $me->_get_search_spec( $them, $form, [ keys %modifiers ] ); | |||||
2609 | |||||||
2610 | # rename modifiers for SQL::Abstract - taking care not to autovivify entries | ||||||
2611 | 0 | $search_modifiers{ $modifiers{ $_ } } = delete( $search_modifiers{ $_ } ) | |||||
2612 | 0 | for grep { $search_modifiers{ $_ } } keys %modifiers; | |||||
2613 | |||||||
2614 | # return everything if no search terms specified | ||||||
2615 | 0 | 0 | unless ( %search_data ) | ||||
2616 | { | ||||||
2617 | 0 | $search_data{1} = 1; | |||||
2618 | 0 | $search_modifiers{cmp} = '='; | |||||
2619 | } | ||||||
2620 | |||||||
2621 | 0 | 0 | my @search = %search_modifiers ? ( \%search_data, \%search_modifiers ) : %search_data; | ||||
2622 | |||||||
2623 | 0 | return $them->search_where( @search ); | |||||
2624 | } | ||||||
2625 | |||||||
2626 | =item find_or_create_from_form | ||||||
2627 | |||||||
2628 | Does a C |
||||||
2629 | |||||||
2630 | =cut | ||||||
2631 | |||||||
2632 | sub find_or_create_from_form | ||||||
2633 | { | ||||||
2634 | 0 | 0 | 1 | my ( $them, $form ) = @_; | |||
2635 | |||||||
2636 | 0 | 0 | Carp::croak "find_or_create_from_form can only be called as a class method" if ref $them; | ||||
2637 | |||||||
2638 | 0 | $them->__form_builder_subclass__->_run_find_or_create_from_form( $them, $form ); | |||||
2639 | } | ||||||
2640 | |||||||
2641 | sub _run_find_or_create_from_form | ||||||
2642 | { | ||||||
2643 | 0 | 0 | my ( $me, $them, $form ) = @_; | ||||
2644 | |||||||
2645 | 0 | 0 | 0 | return unless $form->submitted && $form->validate; | |||
2646 | |||||||
2647 | 0 | my %search_data = $me->_get_search_spec( $them, $form ); | |||||
2648 | |||||||
2649 | 0 | return $them->find_or_create( \%search_data ); | |||||
2650 | } | ||||||
2651 | |||||||
2652 | =item retrieve_or_create_from_form | ||||||
2653 | |||||||
2654 | Attempts to look up an object. If none exists, a new object is created. | ||||||
2655 | |||||||
2656 | This is similar to C |
||||||
2657 | update pre-existing objects. | ||||||
2658 | |||||||
2659 | =cut | ||||||
2660 | |||||||
2661 | sub retrieve_or_create_from_form | ||||||
2662 | { | ||||||
2663 | 0 | 0 | 1 | my ( $them, $form ) = @_; | |||
2664 | |||||||
2665 | 0 | 0 | Carp::croak "retrieve_or_create_from_form can only be called as a class method" if ref $them; | ||||
2666 | |||||||
2667 | 0 | $them->__form_builder_subclass__->_run_retrieve_or_create_from_form( $them, $form ); | |||||
2668 | } | ||||||
2669 | |||||||
2670 | sub _run_retrieve_or_create_from_form | ||||||
2671 | { | ||||||
2672 | 0 | 0 | my ( $me, $them, $form ) = @_; | ||||
2673 | |||||||
2674 | 0 | 0 | 0 | return unless $form->submitted && $form->validate; | |||
2675 | |||||||
2676 | 0 | my $object = $them->retrieve_from_form( $form ); | |||||
2677 | |||||||
2678 | 0 | 0 | return $object if $object; | ||||
2679 | |||||||
2680 | 0 | $them->create_from_form( $form ); | |||||
2681 | } | ||||||
2682 | |||||||
2683 | |||||||
2684 | =back | ||||||
2685 | |||||||
2686 | =cut | ||||||
2687 | |||||||
2688 | # ---------------------------------------------------------------------------------- validation ----- | ||||||
2689 | |||||||
2690 | sub _valid_map | ||||||
2691 | { | ||||||
2692 | 0 | 0 | my ( $me, $type ) = @_; | ||||
2693 | |||||||
2694 | 0 | return $ValidMap{ $type }; | |||||
2695 | } | ||||||
2696 | |||||||
2697 | # $fb_args is the args hash that will be sent to CGI::FB to construct the form. | ||||||
2698 | # Here we re-write $fb_args->{validate} | ||||||
2699 | sub _setup_auto_validation | ||||||
2700 | { | ||||||
2701 | 0 | 0 | my ( $me, $them, $fb_args ) = @_; | ||||
2702 | |||||||
2703 | # this simply returns either the auto-validation spec (as set up by the caller), or | ||||||
2704 | # undef (if the caller has set up a standard CGI::FB validation spec) | ||||||
2705 | 0 | my %args = $me->_get_auto_validate_args( $them ); | |||||
2706 | |||||||
2707 | 0 | 0 | return unless %args; | ||||
2708 | |||||||
2709 | 0 | my $debug = delete $args{debug}; | |||||
2710 | 0 | 0 | warn "auto-validating $them\n" if $debug; | ||||
2711 | |||||||
2712 | # validate and columns are the same thing. validate matches the terminology | ||||||
2713 | # used in CGI::FB, so it should be retained, but 'columns' is more descriptive, | ||||||
2714 | # and to be preferred | ||||||
2715 | 0 | 0 | 0 | if ( exists $args{validate} and exists $args{columns} ) | |||
2716 | { | ||||||
2717 | 0 | Carp::croak "Automatic validation profile contains both 'validate' and 'columns' entries. " . | |||||
2718 | "Use one or other, not both (they're aliases)"; | ||||||
2719 | } | ||||||
2720 | |||||||
2721 | 0 | 0 | my $v_cols = delete $args{columns} || delete $args{validate} || {}; | ||||
2722 | 0 | 0 | my $skip_cols = delete $args{skip_columns} || []; | ||||
2723 | 0 | 0 | my $match_cols = delete $args{match_columns} || {}; | ||||
2724 | 0 | 0 | my $v_types = delete $args{validate_types} || {}; | ||||
2725 | 0 | 0 | my $match_types = delete $args{match_types} || {}; | ||||
2726 | |||||||
2727 | # anything left over is an error | ||||||
2728 | 0 | 0 | if ( my @unknown = keys %args ) | ||||
2729 | { | ||||||
2730 | 0 | Carp::croak "Unknown keys in auto-validation spec: " . join( ', ', @unknown ); | |||||
2731 | } | ||||||
2732 | |||||||
2733 | 0 | my %skip = map { $_ => 1 } @$skip_cols; | |||||
0 | |||||||
2734 | |||||||
2735 | 0 | my %validate; | |||||
2736 | |||||||
2737 | 0 | foreach my $field ( @{ $fb_args->{fields} } ) | |||||
0 | |||||||
2738 | { | ||||||
2739 | 0 | 0 | my $column = ref $field ? $field : $them->find_column($field); | ||||
2740 | 0 | 0 | my $col_name = ref $field ? $column->name : $field; | ||||
2741 | |||||||
2742 | 0 | 0 | next if $skip{$col_name}; | ||||
2743 | |||||||
2744 | # this will get added at the end | ||||||
2745 | 0 | 0 | next if $v_cols->{$col_name}; | ||||
2746 | |||||||
2747 | # look for columns with options | ||||||
2748 | # TODO - what about related columns? - do not want to add 10^6 db rows to validation | ||||||
2749 | # - the caller just has to set up a different config for these cases | ||||||
2750 | |||||||
2751 | 0 | 0 | my $options = $them->form_builder_defaults->{options} || {}; | ||||
2752 | |||||||
2753 | 0 | my $o = $options->{$col_name}; | |||||
2754 | |||||||
2755 | # $o could be an aref of arefs, each consisting of a value and a label - | ||||||
2756 | # we only want the values. Note that in general, there could be a mix of | ||||||
2757 | # arrayrefs and strings in the options list, e.g. for a leading empty item | ||||||
2758 | 0 | 0 | if ( ref($o) eq 'ARRAY' ) | ||||
2759 | { | ||||||
2760 | 0 | 0 | $o = [ map { ref $_ eq 'ARRAY' ? $_->[0] : $_ } @$o ]; | ||||
0 | |||||||
2761 | } | ||||||
2762 | |||||||
2763 | 0 | 0 | unless ($o) | ||||
2764 | { | ||||||
2765 | # if $fb_args has entries for has_many fields, this will croak | ||||||
2766 | 0 | my $column_meta = $me->table_meta( $them )->column( $col_name ); | |||||
2767 | |||||||
2768 | 0 | 0 | last unless $column_meta; # it's a has_many (or similar) field | ||||
2769 | |||||||
2770 | 0 | my ( $series, undef ) = $column_meta->options; | |||||
2771 | 0 | $o = $series; | |||||
2772 | 0 | 0 | 0 | warn "(Probably) setting validation to options (@$o) for $col_name in $them" | |||
2773 | if ( $debug > 1 and @$o ); | ||||||
2774 | 0 | 0 | undef($o) unless @$o; | ||||
2775 | } | ||||||
2776 | |||||||
2777 | 0 | my $type = $me->table_meta($them)->column_deep_type($col_name); | |||||
2778 | |||||||
2779 | 0 | 0 | die "No type for $col_name in $them" unless $type; | ||||
2780 | |||||||
2781 | 0 | 0 | my $v = $o || $v_types->{$type}; | ||||
2782 | |||||||
2783 | 0 | foreach my $regex ( keys %$match_types ) | |||||
2784 | { | ||||||
2785 | 0 | 0 | last if $v; | ||||
2786 | 0 | 0 | $v = $match_types->{$regex} if $type =~ $regex; | ||||
2787 | } | ||||||
2788 | |||||||
2789 | 0 | foreach my $regex ( keys %$match_cols ) | |||||
2790 | { | ||||||
2791 | 0 | 0 | last if $v; | ||||
2792 | 0 | 0 | $v = $match_cols->{$regex} if $col_name =~ $regex; | ||||
2793 | } | ||||||
2794 | |||||||
2795 | 0 | 0 | my $skip_ts = ( ( $type eq 'timestamp' ) && ! $v ); | ||||
2796 | |||||||
2797 | 0 | 0 | 0 | warn "Skipping $them $col_name [timestamp]\n" if ( $skip_ts and $debug > 1 ); | |||
2798 | |||||||
2799 | 0 | 0 | next if $skip_ts; | ||||
2800 | |||||||
2801 | 0 | 0 | $v ||= $me->_valid_map($type) || ''; | ||||
0 | |||||||
2802 | |||||||
2803 | 0 | 0 | my $fail = "No validate type detected for column $col_name, type $type in $them" | ||||
2804 | unless $v; | ||||||
2805 | |||||||
2806 | 0 | 0 | $fail and $args{strict} ? die $fail : warn $fail; | ||||
0 | |||||||
2807 | |||||||
2808 | 0 | my $type2 = substr( $type, 0, 25 ); | |||||
2809 | 0 | 0 | $type2 .= '...' unless $type2 eq $type; | ||||
2810 | |||||||
2811 | 0 | 0 | warn sprintf "Validating %s %s [%s] as %s\n", $them, $col_name, $type2, $v | ||||
2812 | if $debug > 1; | ||||||
2813 | |||||||
2814 | 0 | 0 | $validate{$col_name} = $v if $v; | ||||
2815 | } | ||||||
2816 | |||||||
2817 | 0 | my $validation = {%validate, %$v_cols}; | |||||
2818 | |||||||
2819 | 0 | 0 | if ($debug) | ||||
2820 | { | ||||||
2821 | 0 | 0 | Data::Dumper->require || die $@; | ||||
2822 | 0 | 0 | my $label = ref($them) ? ref($them) . "($them)" : $them; | ||||
2823 | 0 | warn "Setting up validation for $label: ".Data::Dumper::Dumper($validation); | |||||
2824 | } | ||||||
2825 | |||||||
2826 | 0 | $fb_args->{validate} = $validation; | |||||
2827 | |||||||
2828 | 0 | return; | |||||
2829 | } | ||||||
2830 | |||||||
2831 | sub _get_auto_validate_args | ||||||
2832 | { | ||||||
2833 | 0 | 0 | my ( $me, $them ) = @_; | ||||
2834 | |||||||
2835 | 0 | my $fb_defaults = $them->form_builder_defaults; | |||||
2836 | |||||||
2837 | 0 | 0 | 0 | if ( %{ $fb_defaults->{validate} || {} } && %{ $fb_defaults->{auto_validate} || {} } ) | |||
0 | 0 | ||||||
0 | 0 | ||||||
2838 | { | ||||||
2839 | 0 | Carp::croak 'Got validation AND auto-validation settings in form_builder_defaults - ' . | |||||
2840 | 'should only have one or the other'; | ||||||
2841 | } | ||||||
2842 | |||||||
2843 | # don't do auto-validation if the caller has set up a standard CGI::FB validation spec | ||||||
2844 | 0 | 0 | return if %{ $fb_defaults->{validate} || {} }; | ||||
0 | 0 | ||||||
2845 | |||||||
2846 | # stop lots of warnings when testing debug value, and ensure something is set so the cfg exists test passes | ||||||
2847 | 0 | 0 | $fb_defaults->{auto_validate}->{debug} ||= 0; | ||||
2848 | |||||||
2849 | 0 | return %{ $fb_defaults->{auto_validate} }; | |||||
0 | |||||||
2850 | } | ||||||
2851 | |||||||
2852 | # ---------------------------------------------------------------------------------- / validation ----- | ||||||
2853 | |||||||
2854 | =head1 TODO | ||||||
2855 | |||||||
2856 | has_many fields are not currently being validated (the code to set up the validation config | ||||||
2857 | was choking on has_many columns, so for now, they're ignored). | ||||||
2858 | |||||||
2859 | Use the proper column accessors (i.e. $column->name for form field names, $column->accessor | ||||||
2860 | for 'get', and $column->mutator foe 'set' operations). | ||||||
2861 | |||||||
2862 | Add support for local plugins - i.e. specify a custom namespace to search for plugins, before | ||||||
2863 | searching the CDBI::FB::Plugin namespace. | ||||||
2864 | |||||||
2865 | Better merging of attributes. For instance, it'd be nice to set some field attributes | ||||||
2866 | (e.g. size or type) in C |
||||||
2867 | generated and added to C<%args>. | ||||||
2868 | |||||||
2869 | Regex and column type entries for C |
||||||
2870 | |||||||
2871 | Use preprocessors in form_has_a, form_has_many and form_might_have. | ||||||
2872 | |||||||
2873 | Transaction support - see http://search.cpan.org/~tmtm/Class-DBI-0.96/lib/Class/DBI.pm#TRANSACTIONS | ||||||
2874 | and http://wiki.class-dbi.com/index.cgi?AtomicUpdates | ||||||
2875 | |||||||
2876 | Wrap the call to C<$form_modify> in an eval, and provide a better diagnostic if the call | ||||||
2877 | fails because it's trying to handle a relationship that has not yet been coded - e.g. is_a | ||||||
2878 | |||||||
2879 | Store CDBI errors somewhere on the form. For instance, if C |
||||||
2880 | no object could be retrieved using the form data. | ||||||
2881 | |||||||
2882 | Detect binary data and build a file upload widget. | ||||||
2883 | |||||||
2884 | C |
||||||
2885 | |||||||
2886 | C |
||||||
2887 | |||||||
2888 | Figure out how to build a form for a related column when starting from a class, not an object | ||||||
2889 | (pointed out by Peter Speltz). E.g. | ||||||
2890 | |||||||
2891 | my $related = $object->some_col; | ||||||
2892 | |||||||
2893 | print $related->as_form->render; | ||||||
2894 | |||||||
2895 | will not work if $object is a class. Have a look at Maypole::Model::CDBI::related_class. | ||||||
2896 | |||||||
2897 | Integrate fields from a related class object into the same form (e.g. show address fields | ||||||
2898 | in a person form, where person has_a address). B |
||||||
2899 | B |
||||||
2900 | will be merged into C |
||||||
2901 | |||||||
2902 | C<_splice_form> needs to handle custom setup for more relationship types. | ||||||
2903 | |||||||
2904 | =head1 AUTHOR | ||||||
2905 | |||||||
2906 | David Baird, C<< |
||||||
2907 | |||||||
2908 | =head1 BUGS | ||||||
2909 | |||||||
2910 | If no fields are explicitly required, then *all* fields will become required automatically, because | ||||||
2911 | CGI::FormBuilder by default makes any field with validation become required, unless there is at least | ||||||
2912 | 1 field specified as required. | ||||||
2913 | |||||||
2914 | Please report any bugs or feature requests to | ||||||
2915 | C |
||||||
2916 | L |
||||||
2917 | I will be notified, and then you'll automatically be notified of progress on | ||||||
2918 | your bug as I make changes. | ||||||
2919 | |||||||
2920 | Looking at the code (0.32), I suspect updates to has_many accessors are not implemented, since the update | ||||||
2921 | methods only fetch data for columns( 'All' ), which doesn't include has_many accessors/mutators. | ||||||
2922 | |||||||
2923 | =head1 ACKNOWLEDGEMENTS | ||||||
2924 | |||||||
2925 | The following people have provided useful discussions, bug reports, and patches: | ||||||
2926 | |||||||
2927 | Dave Howorth, James Tolley, Ron McClain, David Kamholz. | ||||||
2928 | |||||||
2929 | =head1 COPYRIGHT & LICENSE | ||||||
2930 | |||||||
2931 | Copyright 2005 David Baird, All Rights Reserved. | ||||||
2932 | |||||||
2933 | This program is free software; you can redistribute it and/or modify it | ||||||
2934 | under the same terms as Perl itself. | ||||||
2935 | |||||||
2936 | =cut | ||||||
2937 | |||||||
2938 | 1; # End of Class::DBI::Plugin::FormBuilder | ||||||
2939 | |||||||
2940 | __END__ |