| 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__ |