File Coverage

blib/lib/HTML/FormHandler/BuildFields.pm
Criterion Covered Total %
statement 238 242 98.3
branch 119 136 87.5
condition 58 69 84.0
subroutine 24 24 100.0
pod 0 5 0.0
total 439 476 92.2


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.40068';
4 143     143   97771 use Moose::Role;
  143         424  
  143         1208  
5 143     143   793502 use Try::Tiny;
  143         449  
  143         10146  
6 143     143   960 use Class::Load qw/ load_optional_class /;
  143         703  
  143         6245  
7 143     143   1689 use namespace::autoclean;
  143         382  
  143         1450  
8 143     143   11323 use HTML::FormHandler::Merge ('merge');
  143         441  
  143         6536  
9 143     143   886 use Data::Clone;
  143         350  
  143         324579  
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 265     265 0 8748 sub default_build_include { [] }
21              
22             sub has_field_list {
23 394     394 0 1316 my ( $self, $field_list ) = @_;
24 394   33     15360 $field_list ||= $self->field_list;
25 394 100       1882 if ( ref $field_list eq 'HASH' ) {
    50          
26 376 100       858 return $field_list if ( scalar keys %{$field_list} );
  376         1770  
27             }
28             elsif ( ref $field_list eq 'ARRAY' ) {
29 18 50       46 return $field_list if ( scalar @{$field_list} );
  18         89  
30             }
31 375         1113 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 397     397   6076 my $self = shift;
45              
46 397         2647 my $meta_flist = $self->_build_meta_field_list;
47              
48 397 100       3117 $self->_process_field_array( $meta_flist, 0 ) if $meta_flist;
49 394         3074 my $flist = $self->has_field_list;
50 394 100       1430 if( $flist ) {
51 19 100 100     156 if( ref($flist) eq 'ARRAY' && ref( $flist->[0] ) eq 'HASH' ) {
52 4         18 $self->_process_field_array( $flist );
53             }
54             else {
55 15         90 $self->_process_field_list( $flist );
56             }
57             }
58 394 50       13352 my $mlist = $self->model_fields if $self->fields_from_model;
59 394 50       1387 $self->_process_field_list( $mlist ) if $mlist;
60              
61 394 100       14441 return unless $self->has_fields;
62              
63 264         1930 $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 397     397   1017 my $self = shift;
72 397         1317 my $field_list = [];
73              
74 397         2245 foreach my $sc ( reverse $self->meta->linearized_isa ) {
75 1702         20687 my $meta = $sc->meta;
76 1702 50       35882 if ( $meta->can('calculate_all_roles') ) {
77 1702         6107 foreach my $role ( reverse $meta->calculate_all_roles ) {
78 4456 100 100     128038 if ( $role->can('field_list') && $role->has_field_list ) {
79 11         31 foreach my $fld_def ( @{ $role->field_list } ) {
  11         331  
80 51         105 push @$field_list, $fld_def;
81             }
82             }
83             }
84             }
85 1702 100 100     66254 if ( $meta->can('field_list') && $meta->has_field_list ) {
86 257         737 foreach my $fld_def ( @{ $meta->field_list } ) {
  257         8171  
87 989         2458 push @$field_list, $fld_def;
88             }
89             }
90             }
91 397 100       2169 return $field_list if scalar @$field_list;
92             }
93              
94             sub _process_field_list {
95 15     15   48 my ( $self, $flist ) = @_;
96              
97 15 100       70 if ( ref $flist eq 'ARRAY' ) {
98 14         86 $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   49 my ( $self, $fields ) = @_;
105              
106 14         246 $fields = clone( $fields );
107 14         43 my @new_fields;
108 14         56 while (@$fields) {
109 43         98 my $name = shift @$fields;
110 43         80 my $attr = shift @$fields;
111 43 100       130 unless ( ref $attr eq 'HASH' ) {
112 14         44 $attr = { type => $attr };
113             }
114 43         228 push @new_fields, { name => $name, %$attr };
115             }
116 14         83 return \@new_fields;
117             }
118              
119             # loop through array of field hashrefs
120             sub _process_field_array {
121 272     272   936 my ( $self, $fields ) = @_;
122              
123             # clone and, optionally, filter fields
124 272         1779 $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 272         920 my $num_fields = scalar @$fields;
129 272         657 my $num_dots = 0;
130 272         689 my $count_fields = 0;
131 272         1279 while ( $count_fields < $num_fields ) {
132 344         1049 foreach my $field (@$fields) {
133 1618         5277 my $count = ( $field->{name} =~ tr/\.// );
134 1618 100       5276 next unless $count == $num_dots;
135 1087         5176 $self->_make_field($field);
136 1084         3645 $count_fields++;
137             }
138 341         3774 $num_dots++;
139             }
140             }
141              
142             sub clean_fields {
143 272     272 0 919 my ( $self, $fields ) = @_;
144 272 100       10726 if( $self->has_include ) {
145 3         6 my @fields;
146 3         6 my %include = map { $_ => 1 } @{ $self->include };
  8         26  
  3         86  
147 3         12 foreach my $fld ( @$fields ) {
148 16 100       80 push @fields, clone($fld) if exists $include{$fld->{name}};
149             }
150 3         12 return \@fields;
151             }
152 269         6641 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 1087     1087   3116 my ( $self, $field_attr ) = @_;
160              
161 1087   100     5675 my $type = $field_attr->{type} ||= 'Text';
162 1087         2773 my $name = $field_attr->{name};
163              
164 1087         2175 my $do_update;
165 1087 100       4318 if ( $name =~ /^\+(.*)/ ) {
166 4         15 $field_attr->{name} = $name = $1;
167 4         11 $do_update = 1;
168             }
169              
170 1087         5122 my $class = $self->_find_field_class( $type, $name );
171              
172 1086         8281 my $parent = $self->_find_parent( $field_attr );
173              
174 1086 100       6427 $field_attr = $self->_merge_updates( $field_attr, $class ) unless $do_update;
175              
176 1084         6403 my $field = $self->_update_or_create( $parent, $field_attr, $class, $do_update );
177              
178 1084 100       6594 $self->form->add_to_index( $field->full_name => $field ) if $self->form;
179             }
180              
181             sub _make_adhoc_field {
182 36     36   145 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         118 my $parent = delete $field_attr->{parent};
187 36         110 my $form = delete $field_attr->{form};
188 36         201 $field_attr = $self->_merge_updates( $field_attr, $class );
189 36         122 $field_attr->{parent} = $parent;
190 36         112 $field_attr->{form} = $form;
191 36         189 my $field = $self->new_field_with_traits( $class, $field_attr );
192 36         193 return $field;
193             }
194              
195             sub _find_field_class {
196 1087     1087   3511 my ( $self, $type, $name ) = @_;
197              
198 1087         35253 my $field_ns = $self->field_name_space;
199 1087         2839 my @classes;
200             # '+'-prefixed fields could be full namespaces
201 1087 100       4202 if ( $type =~ s/^\+// )
202             {
203 15         52 push @classes, $type;
204             }
205 1087         3264 foreach my $ns ( @$field_ns, 'HTML::FormHandler::Field', 'HTML::FormHandlerX::Field' )
206             {
207 2194         6987 push @classes, $ns . "::" . $type;
208             }
209             # look for Field in possible namespaces
210 1087         2244 my $class;
211 1087         2631 foreach my $try ( @classes ) {
212 1105 100       14232 last if $class = load_optional_class($try) ? $try : undef;
    100          
213             }
214 1087 100       57139 die "Could not load field class '$type' for field '$name'"
215             unless $class;
216              
217 1086         4474 return $class;
218             }
219              
220             sub _find_parent {
221 1086     1086   3481 my ( $self, $field_attr ) = @_;
222              
223             # parent and name correction for names with dots
224 1086         2392 my $parent;
225 1086 100 100     9943 if ( $field_attr->{name} =~ /\./ ) {
    100          
226 210         1188 my @names = split /\./, $field_attr->{name};
227 210         585 my $simple_name = pop @names;
228 210         688 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         1518 $parent = $self->field($parent_name, undef, $self);
233 210 50       776 if ($parent) {
234 210 50       1155 die "The parent of field " . $field_attr->{name} . " is not a Compound Field"
235             unless $parent->isa('HTML::FormHandler::Field::Compound');
236 210         716 $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         190 $parent = $self;
245             }
246              
247             # get full_name
248 1086         3362 my $full_name = $field_attr->{name};
249             $full_name = $parent->full_name . "." . $field_attr->{name}
250 1086 100       4450 if $parent;
251 1086         3426 $field_attr->{full_name} = $full_name;
252 1086         3030 return $parent;
253              
254             }
255              
256             sub _merge_updates {
257 1118     1118   3532 my ( $self, $field_attr, $class ) = @_;
258              
259             # If there are field_traits at the form level, prepend them
260 1118         2512 my $field_updates;
261 1118 100       5631 unshift @{$field_attr->{traits}}, @{$self->form->field_traits} if $self->form;
  1097         4056  
  1097         4850  
262             # use full_name for updates from form, name for updates from compound field
263 1118   66     5803 my $full_name = delete $field_attr->{full_name} || $field_attr->{name};
264 1118         3020 my $name = $field_attr->{name};
265              
266 1118         2893 my $single_updates = {}; # updates that apply to a single field
267 1118         2793 my $all_updates = {}; # updates that apply to all fields
268             # get updates from form update_subfields and widget_tags
269 1118 100       5668 if ( $self->form ) {
270 1097         4954 $field_updates = $self->form->update_subfields;
271 1097 100       4762 if ( keys %$field_updates ) {
272 147   100     844 $all_updates = $field_updates->{all} || {};
273 147         459 $single_updates = $field_updates->{$full_name};
274 147 100       548 if ( exists $field_updates->{by_flag} ) {
275 78         353 $all_updates = $self->by_flag_updates( $field_attr, $class, $field_updates, $all_updates );
276             }
277 147 100 100     721 if ( exists $field_updates->{by_type} &&
278             exists $field_updates->{by_type}->{$field_attr->{type}} ) {
279 2         13 $all_updates = merge( $field_updates->{by_type}->{$field_attr->{type}}, $all_updates );
280             }
281             }
282             # merge widget tags into 'all' updates
283 1097 100       5281 if( $self->form->has_widget_tags ) {
284 2         30 $all_updates = merge( $all_updates, { tags => $self->form->widget_tags } );
285             }
286             }
287             # get updates from compound field update_subfields and widget_tags
288 1118 100       6061 if ( $self->has_flag('is_compound') ) {
289 79         2408 my $comp_field_updates = $self->update_subfields;
290 79         214 my $comp_all_updates = {};
291 79         179 my $comp_single_updates = {};
292             # -- compound 'all' updates --
293 79 100       302 if ( keys %$comp_field_updates ) {
294 3   50     11 $comp_all_updates = $comp_field_updates->{all} || {};
295             # don't use full_name. varies depending on parent field name
296 3   100     13 $comp_single_updates = $comp_field_updates->{$name} || {};
297 3 50       10 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     10 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       3002 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       322 $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       351 $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 1118 100       5564 $field_attr = merge( $field_attr, $all_updates ) if keys %$all_updates;
320 1118 100       4497 $field_attr = merge( $single_updates, $field_attr ) if keys %$single_updates;
321              
322             # get the widget and widget_wrapper from form
323 1118 50 66     5747 unless( $self->form && $self->form->no_widgets ) {
324             # widget
325 1118         3158 my $widget = $field_attr->{widget};
326 1118 100       5345 unless( $widget ) {
327 1072         5657 my $attr = $class->meta->find_attribute_by_name( 'widget' );
328 1072 50       99633 $widget = $attr->default if $attr;
329             }
330 1118 100       11458 $widget = '' if $widget eq 'None';
331             # widget wrapper
332 1118         3116 my $widget_wrapper = $field_attr->{widget_wrapper};
333 1118 100       3751 unless( $widget_wrapper ) {
334 1092         4302 my $attr = $class->meta->get_attribute('widget_wrapper');
335 1092 50       27194 $widget_wrapper = $attr->default if $attr;
336 1092 100 33     6371 $widget_wrapper ||= $self->form->widget_wrapper if $self->form;
337 1092   100     6247 $widget_wrapper ||= 'Simple';
338 1092         3491 $field_attr->{widget_wrapper} = $widget_wrapper;
339             }
340             # add widget and wrapper roles to field traits
341 1118 100       3766 if ( $widget ) {
342 1107         7163 my $widget_role = $self->get_widget_role( $widget, 'Field' );
343 1105         66485 push @{$field_attr->{traits}}, $widget_role;
  1105         4329  
344             }
345 1116 50       4008 if ( $widget_wrapper ) {
346 1116         4910 my $wrapper_role = $self->get_widget_role( $widget_wrapper, 'Wrapper' );
347 1116         51198 push @{$field_attr->{traits}}, $wrapper_role;
  1116         4266  
348             }
349             }
350 1116         5295 return $field_attr;
351             }
352              
353             sub by_flag_updates {
354 78     78 0 250 my ( $self, $field_attr, $class, $field_updates, $all_updates ) = @_;
355              
356 78         191 my $by_flag = $field_updates->{by_flag};
357 78 100 100     930 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         499 $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         522 $all_updates = merge( $field_updates->{by_flag}->{compound}, $all_updates );
365             }
366 78         11761 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 1084     1084   3684 my ( $self, $parent, $field_attr, $class, $do_update ) = @_;
377              
378 1084   66     6598 $parent ||= $self->form;
379 1084         3463 $field_attr->{parent} = $parent;
380 1084 100       6000 $field_attr->{form} = $self->form if $self->form;
381 1084         7275 my $index = $parent->field_index( $field_attr->{name} );
382 1084         2462 my $field;
383 1084 100       4037 if ( defined $index ) {
384 14 100       44 if ($do_update) # this field started with '+'. Update.
385             {
386 4         125 $field = $parent->field( $field_attr->{name} );
387 4 50       15 die "Field to update for " . $field_attr->{name} . " not found"
388             unless $field;
389 4         10 foreach my $key ( keys %{$field_attr} ) {
  4         19  
390 24 100 100     179 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       144 $field->$key( $field_attr->{$key} )
393             if $field->can($key);
394             }
395             }
396             else # replace existing field
397             {
398 10         35 $field = $self->new_field_with_traits( $class, $field_attr);
399 10         363 $parent->set_field_at( $index, $field );
400             }
401             }
402             else # new field
403             {
404 1070         5720 $field = $self->new_field_with_traits( $class, $field_attr);
405 1070         37975 $parent->add_field($field);
406             }
407 1084 100 100     29952 $field->form->add_repeatable_field($field)
408             if ( $field->form && $field->has_flag('is_repeatable') );
409 1084         3773 return $field;
410             }
411              
412             sub new_field_with_traits {
413 1116     1116 0 3749 my ( $self, $class, $field_attr ) = @_;
414              
415 1116   50     5051 my $traits = delete $field_attr->{traits} || [];
416 1116 50       4164 if( @$traits ) {
417 1116         10889 $class = $class->with_traits( @$traits );
418 1116         6706 $class->meta->make_immutable;
419             }
420 1116         217009 my $field = $class->new( %{$field_attr} );
  1116         42571  
421              
422 1116         5268 return $field;
423             }
424              
425             sub _order_fields {
426 264     264   692 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 264         639 my $order = 0;
437 264         9253 foreach my $field ( $self->all_fields ) {
438 863 100       23766 $order++ if $field->order > $order;
439             }
440 264         715 $order++;
441             # number all unordered fields
442 264         9401 foreach my $field ( $self->all_fields ) {
443 863 100       21634 $field->order($order) unless $field->order;
444 863         2863 $order++;
445             }
446             }
447              
448 143     143   1606 use namespace::autoclean;
  143         452  
  143         1012  
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.40068
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) 2017 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