File Coverage

blib/lib/HTML/FormHandler/BuildFields.pm
Criterion Covered Total %
statement 238 242 98.3
branch 119 136 87.5
condition 57 69 82.6
subroutine 24 24 100.0
pod 0 5 0.0
total 438 476 92.0


line stmt bran cond sub pod time code
1             package HTML::FormHandler::BuildFields;
2             # ABSTRACT: role to build field array
3             $HTML::FormHandler::BuildFields::VERSION = '0.40067';
4 141     141   75402 use Moose::Role;
  141         232  
  141         861  
5 141     141   492979 use Try::Tiny;
  141         242  
  141         8265  
6 141     141   646 use Class::Load qw/ load_optional_class /;
  141         200  
  141         5915  
7 141     141   573 use namespace::autoclean;
  141         877  
  141         1127  
8 141     141   7777 use HTML::FormHandler::Merge ('merge');
  141         225  
  141         5319  
9 141     141   566 use Data::Clone;
  141         184  
  141         295480  
10              
11              
12             has 'fields_from_model' => ( isa => 'Bool', is => 'rw' );
13              
14             has 'field_list' => ( isa => 'HashRef|ArrayRef', is => 'rw', default => sub { {} } );
15              
16             has 'build_include_method' => ( is => 'ro', isa => 'CodeRef', traits => ['Code'],
17             default => sub { \&default_build_include }, handles => { build_include => 'execute_method' } );
18             has 'include' => ( is => 'rw', isa => 'ArrayRef', traits => ['Array'], builder => 'build_include',
19             lazy => 1, handles => { has_include => 'count' } );
20 263     263 0 7534 sub default_build_include { [] }
21              
22             sub has_field_list {
23 392     392 0 652 my ( $self, $field_list ) = @_;
24 392   33     12983 $field_list ||= $self->field_list;
25 392 100       1377 if ( ref $field_list eq 'HASH' ) {
    50          
26 374 100       500 return $field_list if ( scalar keys %{$field_list} );
  374         1308  
27             }
28             elsif ( ref $field_list eq 'ARRAY' ) {
29 18 50       24 return $field_list if ( scalar @{$field_list} );
  18         66  
30             }
31 373         670 return;
32             }
33              
34              
35             # This is the only entry point for this file. It processes the
36             # various methods of field definition (has_field plus the attrs above),
37             # creates objects for fields and writes them into the 'fields' attr
38             # on the base object.
39             #
40             # calls routines to process various field lists
41             # orders the fields after processing in order to skip
42             # fields which have had the 'order' attribute set
43             sub _build_fields {
44 395     395   4152 my $self = shift;
45              
46 395         2046 my $meta_flist = $self->_build_meta_field_list;
47              
48 395 100       2356 $self->_process_field_array( $meta_flist, 0 ) if $meta_flist;
49 392         2452 my $flist = $self->has_field_list;
50 392 100       950 if( $flist ) {
51 19 100 100     120 if( ref($flist) eq 'ARRAY' && ref( $flist->[0] ) eq 'HASH' ) {
52 4         14 $self->_process_field_array( $flist );
53             }
54             else {
55 15         83 $self->_process_field_list( $flist );
56             }
57             }
58 392 50       13976 my $mlist = $self->model_fields if $self->fields_from_model;
59 392 50       952 $self->_process_field_list( $mlist ) if $mlist;
60              
61 392 100       12832 return unless $self->has_fields;
62              
63 262         1467 $self->_order_fields;
64              
65             }
66              
67              
68             # loops through all inherited classes and composed roles
69             # to find fields specified with 'has_field'
70             sub _build_meta_field_list {
71 395     395   634 my $self = shift;
72 395         756 my $field_list = [];
73              
74 395         1738 foreach my $sc ( reverse $self->meta->linearized_isa ) {
75 1694         13598 my $meta = $sc->meta;
76 1694 50       19981 if ( $meta->can('calculate_all_roles') ) {
77 1694         3750 foreach my $role ( reverse $meta->calculate_all_roles ) {
78 4433 100 100     90837 if ( $role->can('field_list') && $role->has_field_list ) {
79 11         15 foreach my $fld_def ( @{ $role->field_list } ) {
  11         300  
80 51         66 push @$field_list, $fld_def;
81             }
82             }
83             }
84             }
85 1694 100 100     55099 if ( $meta->can('field_list') && $meta->has_field_list ) {
86 255         827 foreach my $fld_def ( @{ $meta->field_list } ) {
  255         7161  
87 983         1456 push @$field_list, $fld_def;
88             }
89             }
90             }
91 395 100       1986 return $field_list if scalar @$field_list;
92             }
93              
94             sub _process_field_list {
95 15     15   27 my ( $self, $flist ) = @_;
96              
97 15 100       47 if ( ref $flist eq 'ARRAY' ) {
98 14         51 $self->_process_field_array( $self->_array_fields( $flist ) );
99             }
100             }
101              
102             # munges the field_list array into an array of field attributes
103             sub _array_fields {
104 14     14   23 my ( $self, $fields ) = @_;
105              
106 14         209 $fields = clone( $fields );
107 14         26 my @new_fields;
108 14         39 while (@$fields) {
109 43         47 my $name = shift @$fields;
110 43         40 my $attr = shift @$fields;
111 43 100       84 unless ( ref $attr eq 'HASH' ) {
112 14         32 $attr = { type => $attr };
113             }
114 43         167 push @new_fields, { name => $name, %$attr };
115             }
116 14         57 return \@new_fields;
117             }
118              
119             # loop through array of field hashrefs
120             sub _process_field_array {
121 270     270   527 my ( $self, $fields ) = @_;
122              
123             # clone and, optionally, filter fields
124 270         1398 $fields = $self->clean_fields( $fields );
125             # the point here is to process fields in the order parents
126             # before children, so we process all fields with no dots
127             # first, then one dot, then two dots...
128 270         547 my $num_fields = scalar @$fields;
129 270         436 my $num_dots = 0;
130 270         414 my $count_fields = 0;
131 270         950 while ( $count_fields < $num_fields ) {
132 342         688 foreach my $field (@$fields) {
133 1612         3644 my $count = ( $field->{name} =~ tr/\.// );
134 1612 100       3764 next unless $count == $num_dots;
135 1081         3282 $self->_make_field($field);
136 1078         2171 $count_fields++;
137             }
138 339         2873 $num_dots++;
139             }
140             }
141              
142             sub clean_fields {
143 270     270 0 496 my ( $self, $fields ) = @_;
144 270 100       9278 if( $self->has_include ) {
145 3         3 my @fields;
146 3         6 my %include = map { $_ => 1 } @{ $self->include };
  8         19  
  3         82  
147 3         9 foreach my $fld ( @$fields ) {
148 16 100       65 push @fields, clone($fld) if exists $include{$fld->{name}};
149             }
150 3         12 return \@fields;
151             }
152 267         6044 return clone( $fields );
153             }
154              
155             # Maps the field type to a field class, finds the parent,
156             # sets the 'form' attribute, calls update_or_create
157             # The 'field_attr' hashref must have a 'name' key
158             sub _make_field {
159 1081     1081   1531 my ( $self, $field_attr ) = @_;
160              
161 1081   100     4149 my $type = $field_attr->{type} ||= 'Text';
162 1081         1530 my $name = $field_attr->{name};
163              
164 1081         1140 my $do_update;
165 1081 100       2873 if ( $name =~ /^\+(.*)/ ) {
166 4         11 $field_attr->{name} = $name = $1;
167 4         5 $do_update = 1;
168             }
169              
170 1081         3353 my $class = $self->_find_field_class( $type, $name );
171              
172 1080         4621 my $parent = $self->_find_parent( $field_attr );
173              
174 1080 100       6805 $field_attr = $self->_merge_updates( $field_attr, $class ) unless $do_update;
175              
176 1078         6467 my $field = $self->_update_or_create( $parent, $field_attr, $class, $do_update );
177              
178 1078 100       5143 $self->form->add_to_index( $field->full_name => $field ) if $self->form;
179             }
180              
181             sub _make_adhoc_field {
182 36     36   76 my ( $self, $class, $field_attr ) = @_;
183              
184             # remove and save form & parent, because if the form class has a 'clone'
185             # method, Data::Clone::clone will clone the form
186 36         90 my $parent = delete $field_attr->{parent};
187 36         57 my $form = delete $field_attr->{form};
188 36         143 $field_attr = $self->_merge_updates( $field_attr, $class );
189 36         84 $field_attr->{parent} = $parent;
190 36         76 $field_attr->{form} = $form;
191 36         114 my $field = $self->new_field_with_traits( $class, $field_attr );
192 36         153 return $field;
193             }
194              
195             sub _find_field_class {
196 1081     1081   1782 my ( $self, $type, $name ) = @_;
197              
198 1081         30727 my $field_ns = $self->field_name_space;
199 1081         1597 my @classes;
200             # '+'-prefixed fields could be full namespaces
201 1081 100       2920 if ( $type =~ s/^\+// )
202             {
203 15         33 push @classes, $type;
204             }
205 1081         2048 foreach my $ns ( @$field_ns, 'HTML::FormHandler::Field', 'HTML::FormHandlerX::Field' )
206             {
207 2182         4559 push @classes, $ns . "::" . $type;
208             }
209             # look for Field in possible namespaces
210 1081         1246 my $class;
211 1081         1611 foreach my $try ( @classes ) {
212 1099 100       11190 last if $class = load_optional_class($try) ? $try : undef;
    100          
213             }
214 1081 100       38506 die "Could not load field class '$type' for field '$name'"
215             unless $class;
216              
217 1080         3135 return $class;
218             }
219              
220             sub _find_parent {
221 1080     1080   1702 my ( $self, $field_attr ) = @_;
222              
223             # parent and name correction for names with dots
224 1080         1386 my $parent;
225 1080 100 100     8326 if ( $field_attr->{name} =~ /\./ ) {
    100          
226 210         919 my @names = split /\./, $field_attr->{name};
227 210         677 my $simple_name = pop @names;
228 210         500 my $parent_name = join '.', @names;
229             # use special 'field' method call that starts from
230             # $self, because names aren't always starting from
231             # the form
232 210         1125 $parent = $self->field($parent_name, undef, $self);
233 210 50       477 if ($parent) {
234 210 50       943 die "The parent of field " . $field_attr->{name} . " is not a Compound Field"
235             unless $parent->isa('HTML::FormHandler::Field::Compound');
236 210         499 $field_attr->{name} = $simple_name;
237             }
238             else {
239 0         0 die "did not find parent for field " . $field_attr->{name};
240             }
241             }
242             elsif ( !( $self->form && $self == $self->form ) ) {
243             # set parent
244 72         94 $parent = $self;
245             }
246              
247             # get full_name
248 1080         2126 my $full_name = $field_attr->{name};
249             $full_name = $parent->full_name . "." . $field_attr->{name}
250 1080 100       3126 if $parent;
251 1080         2203 $field_attr->{full_name} = $full_name;
252 1080         1787 return $parent;
253              
254             }
255              
256             sub _merge_updates {
257 1112     1112   3683 my ( $self, $field_attr, $class ) = @_;
258              
259             # If there are field_traits at the form level, prepend them
260 1112         1364 my $field_updates;
261 1112 100       4352 unshift @{$field_attr->{traits}}, @{$self->form->field_traits} if $self->form;
  1091         2770  
  1091         3748  
262             # use full_name for updates from form, name for updates from compound field
263 1112   66     4220 my $full_name = delete $field_attr->{full_name} || $field_attr->{name};
264 1112         1915 my $name = $field_attr->{name};
265              
266 1112         1751 my $single_updates = {}; # updates that apply to a single field
267 1112         1616 my $all_updates = {}; # updates that apply to all fields
268             # get updates from form update_subfields and widget_tags
269 1112 100       4380 if ( $self->form ) {
270 1091         3557 $field_updates = $self->form->update_subfields;
271 1091 100       3368 if ( keys %$field_updates ) {
272 147   100     659 $all_updates = $field_updates->{all} || {};
273 147         320 $single_updates = $field_updates->{$full_name};
274 147 100       413 if ( exists $field_updates->{by_flag} ) {
275 78         286 $all_updates = $self->by_flag_updates( $field_attr, $class, $field_updates, $all_updates );
276             }
277 147 100 100     615 if ( exists $field_updates->{by_type} &&
278             exists $field_updates->{by_type}->{$field_attr->{type}} ) {
279 2         14 $all_updates = merge( $field_updates->{by_type}->{$field_attr->{type}}, $all_updates );
280             }
281             }
282             # merge widget tags into 'all' updates
283 1091 100       4007 if( $self->form->has_widget_tags ) {
284 2         23 $all_updates = merge( $all_updates, { tags => $self->form->widget_tags } );
285             }
286             }
287             # get updates from compound field update_subfields and widget_tags
288 1112 100       4497 if ( $self->has_flag('is_compound') ) {
289 79         2143 my $comp_field_updates = $self->update_subfields;
290 79         125 my $comp_all_updates = {};
291 79         106 my $comp_single_updates = {};
292             # -- compound 'all' updates --
293 79 100       236 if ( keys %$comp_field_updates ) {
294 3   50     9 $comp_all_updates = $comp_field_updates->{all} || {};
295             # don't use full_name. varies depending on parent field name
296 3   100     14 $comp_single_updates = $comp_field_updates->{$name} || {};
297 3 50       7 if ( exists $field_updates->{by_flag} ) {
298 0         0 $comp_all_updates = $self->by_flag_updates( $field_attr, $class, $comp_field_updates, $comp_all_updates );
299             }
300 3 50 33     9 if ( exists $comp_field_updates->{by_type} &&
301             exists $comp_field_updates->{by_type}->{$field_attr->{type}} ) {
302 0         0 $comp_all_updates = merge( $comp_field_updates->{by_type}->{$field_attr->{type}}, $comp_all_updates );
303             }
304             }
305 79 50       2690 if( $self->has_widget_tags ) {
306 0         0 $comp_all_updates = merge( $comp_all_updates, { tags => $self->widget_tags } );
307             }
308              
309             # merge form 'all' updates, compound field higher precedence
310 79 100       195 $all_updates = merge( $comp_all_updates, $all_updates )
311             if keys %$comp_all_updates;
312             # merge single field updates, compound field higher precedence
313 79 100       244 $single_updates = merge( $comp_single_updates, $single_updates )
314             if keys %$comp_single_updates;
315             }
316              
317             # attributes set on a specific field through update_subfields override has_fields
318             # attributes set by 'all' only happen if no field attributes
319 1112 100       3428 $field_attr = merge( $field_attr, $all_updates ) if keys %$all_updates;
320 1112 100       3096 $field_attr = merge( $single_updates, $field_attr ) if keys %$single_updates;
321              
322             # get the widget and widget_wrapper from form
323 1112 50 66     4305 unless( $self->form && $self->form->no_widgets ) {
324             # widget
325 1112         1849 my $widget = $field_attr->{widget};
326 1112 100       2472 unless( $widget ) {
327 1066         6330 my $attr = $class->meta->find_attribute_by_name( 'widget' );
328 1066 50       59086 $widget = $attr->default if $attr;
329             }
330 1112 100       10744 $widget = '' if $widget eq 'None';
331             # widget wrapper
332 1112         1908 my $widget_wrapper = $field_attr->{widget_wrapper};
333 1112 100       2730 unless( $widget_wrapper ) {
334 1086         3116 my $attr = $class->meta->get_attribute('widget_wrapper');
335 1086 50       14243 $widget_wrapper = $attr->default if $attr;
336 1086 100 33     4939 $widget_wrapper ||= $self->form->widget_wrapper if $self->form;
337 1086   100     2721 $widget_wrapper ||= 'Simple';
338 1086         2169 $field_attr->{widget_wrapper} = $widget_wrapper;
339             }
340             # add widget and wrapper roles to field traits
341 1112 100       2544 if ( $widget ) {
342 1101         5561 my $widget_role = $self->get_widget_role( $widget, 'Field' );
343 1099         41720 push @{$field_attr->{traits}}, $widget_role;
  1099         13171  
344             }
345 1110 50       2822 if ( $widget_wrapper ) {
346 1110         3130 my $wrapper_role = $self->get_widget_role( $widget_wrapper, 'Wrapper' );
347 1110         30514 push @{$field_attr->{traits}}, $wrapper_role;
  1110         3273  
348             }
349             }
350 1110         3457 return $field_attr;
351             }
352              
353             sub by_flag_updates {
354 78     78 0 134 my ( $self, $field_attr, $class, $field_updates, $all_updates ) = @_;
355              
356 78         160 my $by_flag = $field_updates->{by_flag};
357 78 100 66     822 if ( exists $by_flag->{contains} && $field_attr->{is_contains} ) {
    100 100        
    100 100        
358 1         6 $all_updates = merge( $field_updates->{by_flag}->{contains}, $all_updates );
359             }
360             elsif ( exists $by_flag->{repeatable} && $class->meta->find_attribute_by_name('is_repeatable') ) {
361 5         335 $all_updates = merge( $field_updates->{by_flag}->{repeatable}, $all_updates );
362             }
363             elsif ( exists $by_flag->{compound} && $class->meta->find_attribute_by_name('is_compound') ) {
364 3         346 $all_updates = merge( $field_updates->{by_flag}->{compound}, $all_updates );
365             }
366 78         6634 return $all_updates;
367             }
368              
369             # update, replace, or create field
370             # Create makes the field object and passes in the properties as constructor args.
371             # Update changed properties on a previously created object.
372             # Replace overwrites a field with a different configuration.
373             # (The update/replace business is much the same as you'd see with inheritance.)
374             # This function populates/updates the base object's 'field' array.
375             sub _update_or_create {
376 1078     1078   1866 my ( $self, $parent, $field_attr, $class, $do_update ) = @_;
377              
378 1078   66     4548 $parent ||= $self->form;
379 1078         2217 $field_attr->{parent} = $parent;
380 1078 100       6562 $field_attr->{form} = $self->form if $self->form;
381 1078         5618 my $index = $parent->field_index( $field_attr->{name} );
382 1078         1263 my $field;
383 1078 100       2697 if ( defined $index ) {
384 14 100       48 if ($do_update) # this field started with '+'. Update.
385             {
386 4         21 $field = $parent->field( $field_attr->{name} );
387 4 50       12 die "Field to update for " . $field_attr->{name} . " not found"
388             unless $field;
389 4         6 foreach my $key ( keys %{$field_attr} ) {
  4         17  
390 24 100 100     140 next if $key eq 'name' || $key eq 'form' || $key eq 'parent' ||
      100        
      100        
      100        
391             $key eq 'full_name' || $key eq 'type';
392 4 50       245 $field->$key( $field_attr->{$key} )
393             if $field->can($key);
394             }
395             }
396             else # replace existing field
397             {
398 10         23 $field = $self->new_field_with_traits( $class, $field_attr);
399 10         325 $parent->set_field_at( $index, $field );
400             }
401             }
402             else # new field
403             {
404 1064         3792 $field = $self->new_field_with_traits( $class, $field_attr);
405 1064         33452 $parent->add_field($field);
406             }
407 1078 100 100     25753 $field->form->add_repeatable_field($field)
408             if ( $field->form && $field->has_flag('is_repeatable') );
409 1078         2454 return $field;
410             }
411              
412             sub new_field_with_traits {
413 1110     1110 0 2729 my ( $self, $class, $field_attr ) = @_;
414              
415 1110   50     4588 my $traits = delete $field_attr->{traits} || [];
416 1110 50       2970 if( @$traits ) {
417 1110         8614 $class = $class->with_traits( @$traits );
418 1110         5195 $class->meta->make_immutable;
419             }
420 1110         142812 my $field = $class->new( %{$field_attr} );
  1110         36966  
421              
422 1110         3459 return $field;
423             }
424              
425             sub _order_fields {
426 262     262   393 my $self = shift;
427              
428             # order the fields
429             # There's a hole in this... if child fields are defined at
430             # a level above the containing parent, then they won't
431             # exist when this routine is called and won't be ordered.
432             # This probably needs to be moved out of here into
433             # a separate recursive step that's called after build_fields.
434              
435             # get highest order number
436 262         383 my $order = 0;
437 262         8039 foreach my $field ( $self->all_fields ) {
438 857 100       21714 $order++ if $field->order > $order;
439             }
440 262         446 $order++;
441             # number all unordered fields
442 262         7633 foreach my $field ( $self->all_fields ) {
443 857 100       20084 $field->order($order) unless $field->order;
444 857         1995 $order++;
445             }
446             }
447              
448 141     141   890 use namespace::autoclean;
  141         259  
  141         687  
449             1;
450              
451             __END__
452              
453             =pod
454              
455             =encoding UTF-8
456              
457             =head1 NAME
458              
459             HTML::FormHandler::BuildFields - role to build field array
460              
461             =head1 VERSION
462              
463             version 0.40067
464              
465             =head1 SYNOPSIS
466              
467             These are the methods that are necessary to build the fields arrays
468             in a form. This is a role which is composed into L<HTML::FormHandler>.
469              
470             Internal code only. This role has no user interfaces.
471              
472             =head1 AUTHOR
473              
474             FormHandler Contributors - see HTML::FormHandler
475              
476             =head1 COPYRIGHT AND LICENSE
477              
478             This software is copyright (c) 2016 by Gerda Shank.
479              
480             This is free software; you can redistribute it and/or modify it under
481             the same terms as the Perl 5 programming language system itself.
482              
483             =cut