File Coverage

blib/lib/HTML/FormFu/Element/Repeatable.pm
Criterion Covered Total %
statement 154 154 100.0
branch 35 44 79.5
condition 9 14 64.2
subroutine 16 16 100.0
pod 2 5 40.0
total 216 233 92.7


line stmt bran cond sub pod time code
1             package HTML::FormFu::Element::Repeatable;
2              
3 34     34   1006 use strict;
  34         81  
  34         1654  
4             our $VERSION = '2.05'; # VERSION
5              
6 34     34   125 use Moose;
  34         42  
  34         236  
7 34     34   148963 use MooseX::Attribute::FormFuChained;
  34         54  
  34         1165  
8             extends 'HTML::FormFu::Element::Block';
9              
10 34     34   127 use HTML::FormFu::Util qw( DEBUG_PROCESS debug );
  34         44  
  34         2237  
11 34     34   130 use List::Util qw( first );
  34         42  
  34         1745  
12 34     34   130 use Carp qw( croak );
  34         47  
  34         40963  
13              
14             has counter_name => ( is => 'rw', traits => ['FormFuChained'] );
15              
16             has _original_elements => ( is => 'rw' );
17              
18             has increment_field_names => (
19             is => 'rw',
20             default => 1,
21             lazy => 1,
22             traits => ['FormFuChained'],
23             );
24              
25             # This attribute is currently not documented as FF::Model::HashRef
26             # only supports '_'
27              
28             has repeatable_delimiter => (
29             is => 'rw',
30             default => '_',
31             lazy => 1,
32             traits => ['FormFuChained'],
33             );
34              
35             after BUILD => sub {
36             my $self = shift;
37              
38             $self->filename('repeatable');
39             $self->is_repeatable(1);
40              
41             return;
42             };
43              
44             sub repeat {
45 46     46 1 435 my ( $self, $count ) = @_;
46              
47 46 50       223 croak "invalid number to repeat"
48             if $count !~ /^[0-9]+\z/;
49              
50 46         64 my $children;
51              
52 46 100       1278 if ( $self->_original_elements ) {
53              
54             # repeat() has already been called
55 6         168 $children = $self->_original_elements;
56             }
57             else {
58 40         940 $children = $self->_elements;
59              
60 40         1027 $self->_original_elements($children);
61             }
62              
63 46 50       138 croak "no child elements to repeat"
64             if !@$children;
65              
66 46         1086 $self->_elements( [] );
67              
68 46 100       114 return [] if !$count;
69              
70             # switch behaviour
71             # If nested_name is set, we add the repeatable counter to the name
72             # of the containing block (this repeatable block).
73             # This behaviour eases the creation of client side javascript code
74             # to add and remove repeatable elements client side.
75             # If nested_name is *not* set, we add the repeatable counter to the names
76             # of the child elements (leaves of the element tree).
77 45         1209 my $nested_name = $self->nested_name;
78 45 100 66     249 if ( defined $nested_name && length $nested_name ) {
79 18         50 return $self->_repeat_containing_block($count);
80             }
81             else {
82 27         86 return $self->_repeat_child_elements($count);
83             }
84             }
85              
86             sub _repeat_containing_block {
87 18     18   34 my ( $self, $count ) = @_;
88              
89 18         457 my $children = $self->_original_elements;
90              
91             # We must not get 'nested.nested_1' instead of 'nested_1' through the
92             # nested_name attribute of the Repeatable element, thus we extended
93             # FF::Elements::_Field nested_names method to ignore Repeatable elements.
94 18         408 my $nested_name = $self->nested_name;
95 18         534 $self->original_nested_name($nested_name);
96              
97             # delimiter between nested_name and the incremented counter
98 18         512 my $delimiter = $self->repeatable_delimiter;
99              
100 18         24 my @return;
101              
102 18         51 for my $rep ( 1 .. $count ) {
103              
104             # create clones of elements and put them in a new block
105 34         62 my @clones = map { $_->clone } @$children;
  64         199  
106 34         154 my $block = $self->element('Block');
107              
108             # initiate new block with properties of this repeatable
109 34         809 $block->_elements( \@clones );
110 34         100 $block->attributes( $self->attributes );
111 34         772 $block->tag( $self->tag );
112              
113 34         114 $block->repeatable_count($rep);
114              
115 34 50       892 if ( $self->increment_field_names ) {
116              
117             # store the original nested_name attribute for later usage when
118             # building the original nested name
119 34 50       836 $block->original_nested_name( $block->nested_name )
120             if !defined $block->original_nested_name;
121              
122             # create new nested name with repeat counter
123 34         815 $block->nested_name( $nested_name . $delimiter . $rep );
124              
125 34         32 for my $field ( @{ $block->get_fields } ) {
  34         118  
126              
127 74 50       168 if ( defined( my $name = $field->name ) ) {
128              
129             # store original name for later usage when
130             # replacing the field names in constraints
131 74 100       1808 $field->original_name($name)
132             if !defined $field->original_name;
133              
134             # store original nested name for later usage when
135             # replacing the field names in constraints
136 74 100       1838 $field->original_nested_name(
137             $field->build_original_nested_name )
138             if !defined $field->original_nested_name;
139             }
140             }
141             }
142              
143 34         104 _reparent_children($block);
144              
145 34         38 my @fields = @{ $block->get_fields };
  34         93  
146              
147 34         63 for my $field (@fields) {
148 19         50 map { $_->parent($field) }
149 74         1726 @{ $field->_deflators },
150 74         1641 @{ $field->_filters },
151 74         1669 @{ $field->_constraints },
152 74         1653 @{ $field->_inflators },
153 74         1707 @{ $field->_validators },
154 74         1679 @{ $field->_transformers },
155 74         67 @{ $field->_plugins },
  74         1664  
156             ;
157             }
158              
159 34         57 for my $field (@fields) {
160 13         41 map { $_->repeatable_repeat( $self, $block ) }
161 74         82 @{ $field->_constraints };
  74         1665  
162             }
163              
164 34         78 push @return, $block;
165             }
166              
167 18         64 return \@return;
168             }
169              
170             sub get_field_with_original_name {
171 11     11 0 14 my ( $self, $name, $fields ) = @_;
172              
173 23     23   547 my $field = first { $_->original_nested_name eq $name }
174 11         37 grep { defined $_->original_nested_name } @$fields;
  26         632  
175              
176 4     4   89 $field ||= first { $_->original_name eq $name }
177 11   66     45 grep { defined $_->original_name } @$fields;
  6         134  
178              
179 11         23 return $field;
180             }
181              
182             sub _repeat_child_elements {
183 27     27   51 my ( $self, $count ) = @_;
184              
185 27         718 my $children = $self->_original_elements;
186              
187             # delimiter between nested_name and the incremented counter
188 27         743 my $delimiter = $self->repeatable_delimiter;
189              
190 27         39 my @return;
191              
192 27         79 for my $rep ( 1 .. $count ) {
193 51         89 my @clones = map { $_->clone } @$children;
  92         288  
194 51         262 my $block = $self->element('Block');
195              
196 51         1201 $block->_elements( \@clones );
197 51         139 $block->attributes( $self->attributes );
198 51         1199 $block->tag( $self->tag );
199              
200 51         155 $block->repeatable_count($rep);
201              
202 51 100       1382 if ( $self->increment_field_names ) {
203 42         39 for my $field ( @{ $block->get_fields } ) {
  42         132  
204              
205 83 50       173 if ( defined( my $name = $field->name ) ) {
206 83 100       2037 $field->original_name($name)
207             if !defined $field->original_name;
208              
209 83 100       2092 $field->original_nested_name( $field->nested_name )
210             if !defined $field->original_nested_name;
211              
212 83         234 $field->name( ${name} . $delimiter . $rep );
213             }
214             }
215             }
216              
217 51         146 _reparent_children($block);
218              
219 51         57 my @fields = @{ $block->get_fields };
  51         125  
220              
221 51         84 for my $field (@fields) {
222 39         78 map { $_->parent($field) }
223 96         2289 @{ $field->_deflators },
224 96         2243 @{ $field->_filters },
225 96         2256 @{ $field->_constraints },
226 96         2218 @{ $field->_inflators },
227 96         2200 @{ $field->_validators },
228 96         2206 @{ $field->_transformers },
229 96         77 @{ $field->_plugins },
  96         2439  
230             ;
231             }
232              
233 51         76 for my $field (@fields) {
234 36         94 map { $_->repeatable_repeat( $self, $block ) }
235 96         70 @{ $field->_constraints };
  96         2182  
236             }
237              
238 51         115 push @return, $block;
239             }
240              
241 27         87 return \@return;
242             }
243              
244             sub _reparent_children {
245 271     271   231 my $self = shift;
246              
247 271 100       5832 return if !$self->is_block;
248              
249 110         122 for my $child ( @{ $self->get_elements } ) {
  110         313  
250 186         402 $child->parent($self);
251              
252 186         310 _reparent_children($child);
253             }
254             }
255              
256             sub process {
257 53     53 0 77 my $self = shift;
258              
259 53         1364 my $counter_name = $self->counter_name;
260 53         260 my $form = $self->form;
261 53         67 my $count = 1;
262              
263 53 100 66     965 if ( defined $counter_name && defined $form->query ) {
264              
265             # are we in a nested-repeatable?
266 43         56 my $parent = $self;
267              
268 43         133 while ( defined( $parent = $parent->parent ) ) {
269 48         239 my $field
270             = $parent->get_field( { original_name => $counter_name } );
271              
272 48 100       192 if ( defined $field ) {
273 10         30 $counter_name = $field->nested_name;
274 10         15 last;
275             }
276             }
277              
278 43         861 my $input = $form->query->param($counter_name);
279              
280 43 100 66     335 if ( defined $input && $input =~ /^[1-9][0-9]*\z/ ) {
281 42         65 $count = $input;
282             }
283             }
284              
285 53 100       1483 if ( !$self->_original_elements ) {
286 19 50       57 DEBUG_PROCESS && debug("calling \$repeatable->repeat($count)");
287              
288 19         68 $self->repeat($count);
289             }
290              
291 53         281 return $self->SUPER::process(@_);
292             }
293              
294             sub content {
295 29     29 1 51 my $self = shift;
296              
297 29 50       79 croak "Repeatable elements do not support the content() method"
298             if @_;
299              
300 29         89 return;
301             }
302              
303             sub string {
304 22     22 0 41 my ( $self, $args ) = @_;
305              
306 22   50     130 $args ||= {};
307              
308             my $render
309             = exists $args->{render_data}
310             ? $args->{render_data}
311 22 50       174 : $self->render_data_non_recursive;
312              
313             # block template
314              
315 22         42 my @divs = map { $_->render } @{ $self->get_elements };
  40         186  
  22         94  
316              
317 22         75 my $html = join "\n", @divs;
318              
319 22         128 return $html;
320             }
321              
322             __PACKAGE__->meta->make_immutable;
323              
324             1;
325              
326             __END__
327              
328             =head1 NAME
329              
330             HTML::FormFu::Element::Repeatable - repeatable block element
331              
332             =head1 VERSION
333              
334             version 2.05
335              
336             =head1 SYNOPSIS
337              
338             ---
339             elements:
340             - type: Repeatable
341             name: my_rep
342             elements:
343             - name: foo
344             - name: bar
345              
346             Calling C<< $element->repeat(2) >> would result in the following markup:
347              
348             <div>
349             <input name="my_rep.foo_1" type="text" />
350             <input name="my_rep.bar_1" type="text" />
351             </div>
352             <div>
353             <input name="my_rep.foo_2" type="text" />
354             <input name="my_rep.bar_2" type="text" />
355             </div>
356              
357             Example of constraints:
358              
359             ----
360             elements:
361             - type: Repeatable
362             name: my_rep
363             elements:
364             - name: id
365              
366             - name: foo
367             constraints:
368             - type: Required
369             when:
370             field: 'my_rep.id' # use full nested-name
371              
372             - name: bar
373             constraints:
374             - type: Equal
375             others: 'my_rep.foo' # use full nested-name
376              
377             =head1 DESCRIPTION
378              
379             Provides a way to extend a form at run-time, by copying and repeating its
380             child elements.
381              
382             The elements intended for copying must be added before L</repeat> is called.
383              
384             Although the Repeatable element inherits from
385             L<Block|HTML::FormFu::Element::Block>, it doesn't generate a block tag
386             around all the repeated elements - instead it places each repeat of the
387             elements in a new L<Block|HTML::FormFu::Element::Block> element, which
388             inherits the Repeatable's display settings, such as L</attributes> and
389             L</tag>.
390              
391             For all constraints attached to fields within a Repeatable block which use
392             either L<others|HTML::FormFu::Role::Constraint::Others/others> or
393             L<when|HTML::FormFu::Constraint/when> containing names of fields within
394             the same Repeatable block, when L<repeat> is called, those names will
395             automatically be updated to the new nested-name for each field (taking
396             into account L<increment_field_names>).
397              
398             =head1 METHODS
399              
400             =head2 repeat
401              
402             Arguments: [$count]
403              
404             Return Value: $arrayref_of_new_child_blocks
405              
406             This method creates C<$count> number of copies of the child elements.
407             If no argument C<$count> is provided, it defaults to C<1>.
408              
409             Note that C<< $form->process >> will call L</repeat> automatically to ensure the
410             initial child elements are correctly set up - unless you call L</repeat>
411             manually first, in which case the child elements you created will be left
412             untouched (otherwise L<process|HTML::FormFu/process> would overwrite your
413             changes).
414              
415             Any subsequent call to L</repeat> will delete the previously copied elements
416             before creating new copies - this means you cannot make repeated calls to
417             L</repeat> within a loop to create more copies.
418              
419             Each copy of the elements returned are contained in a new
420             L<Block|HTML::FormFu::Element::Block> element. For example, calling
421             C<< $element->repeat(2) >> on a Repeatable element containing 2 Text fields
422             would return 2 L<Block|HTML::FormFu::Element::Block> elements, each
423             containing a copy of the 2 Text fields.
424              
425             =head2 counter_name
426              
427             Arguments: $name
428              
429             If true, the L<HTML::FormFu/query> will be searched during
430             L<HTML::FormFu/process> for a parameter with the given name. The value for
431             that parameter will be passed to L</repeat>, to automatically create the
432             new copies.
433              
434             If L</increment_field_names> is true (the default), this is essential: if the
435             elements corresponding to the new fieldnames (foo_1, bar_2, etc.) are not
436             present on the form during L<HTML::FormFu/process>, no Processors
437             (Constraints, etc.) will be run on the fields, and their values will not
438             be returned by L<HTML::FormFu/params> or L<HTML::FormFu/param>.
439              
440             =head2 increment_field_names
441              
442             Arguments: $bool
443              
444             Default Value: 1
445              
446             If true, then all fields will have C<< _n >> appended to their name, where
447             C<n> is the L</repeatable_count> value.
448              
449             =head2 repeatable_count
450              
451             This is set on each new L<Block|HTML::FormFu::Element::Block> element
452             returned by L</repeat>, starting at number C<1>.
453              
454             Because this is an 'inherited accessor' available on all elements, it can be
455             used to determine whether any element is a child of a Repeatable element.
456              
457             Only available after L<repeat> has been called.
458              
459             =head2 repeatable_count_no_inherit
460              
461             A non-inheriting variant of L</repeatable_count>.
462              
463             =head2 nested_name
464              
465             If the L</nested_name> attribute is set, the naming scheme of the Repeatable
466             element's children is switched to add the counter to the repeatable blocks
467             themselves.
468              
469             ---
470             elements:
471             - type: Repeatable
472             nested_name: my_rep
473             elements:
474             - name: foo
475             - name: bar
476              
477             Calling C<< $element->repeat(2) >> would result in the following markup:
478              
479             <div>
480             <input name="my_rep_1.foo" type="text" />
481             <input name="my_rep_1.bar" type="text" />
482             </div>
483             <div>
484             <input name="my_rep_2.foo" type="text" />
485             <input name="my_rep_2.bar" type="text" />
486             </div>
487              
488              
489             Because this is an 'inherited accessor' available on all elements, it can be
490             used to determine whether any element is a child of a Repeatable element.
491              
492             =head2 attributes
493              
494             =head2 attrs
495              
496             Any attributes set will be passed to every repeated Block of elements.
497              
498             ---
499             elements:
500             - type: Repeatable
501             name: my_rep
502             attributes:
503             class: rep
504             elements:
505             - name: foo
506              
507             Calling C<< $element->repeat(2) >> would result in the following markup:
508              
509             <div class="rep">
510             <input name="my_rep.foo_1" type="text" />
511             </div>
512             <div class="rep">
513             <input name="my_rep.foo_2" type="text" />
514             </div>
515              
516             See L<HTML::FormFu/attributes> for details.
517              
518             =head2 tag
519              
520             The L</tag> value will be passed to every repeated Block of elements.
521              
522             ---
523             elements:
524             - type: Repeatable
525             name: my_rep
526             tag: span
527             elements:
528             - name: foo
529              
530             Calling C<< $element->repeat(2) >> would result in the following markup:
531              
532             <span>
533             <input name="my_rep.foo_1" type="text" />
534             </span>
535             <span>
536             <input name="my_rep.foo_2" type="text" />
537             </span>
538              
539             See L<HTML::FormFu::Element::Block/tag> for details.
540              
541             =head2 auto_id
542              
543             As well as the usual substitutions, any instances of C<%r> will be
544             replaced with the value of L</repeatable_count>.
545              
546             See L<HTML::FormFu::Element::Block/auto_id> for further details.
547              
548             ---
549             elements:
550             - type: Repeatable
551             name: my_rep
552             auto_id: "%n_%r"
553             elements:
554             - name: foo
555              
556             Calling C<< $element->repeat(2) >> would result in the following markup:
557              
558             <div>
559             <input name="my_rep.foo_1" id="foo_1" type="text" />
560             </div>
561             <div>
562             <input name="my_rep.foo_2" id="foo_2" type="text" />
563             </div>
564              
565             =head2 content
566              
567             Not supported for Repeatable elements - will throw a fatal error if called as
568             a setter.
569              
570             =head1 CAVEATS
571              
572             =head2 Unsupported Constraints
573              
574             Note that constraints with an L<others|HTML::FormFu::Role::Constraint::Others>
575             method do not work correctly within a Repeatable block. Currently, these are:
576             L<AllOrNone|HTML::FormFu::Constraint::AllOrNone>,
577             L<DependOn|HTML::FormFu::Constraint::DependOn>,
578             L<Equal|HTML::FormFu::Constraint::Equal>,
579             L<MinMaxFields|HTML::FormFu::Constraint::MinMaxFields>,
580             L<reCAPTCHA|HTML::FormFu::Constraint::reCAPTCHA>.
581             Also, the L<CallbackOnce|HTML::FormFu::Constraint::CallbackOnce> constraint
582             won't work within a Repeatable block, as it wouldn't make much sense.
583              
584             =head2 Work-arounds
585              
586             See L<HTML::FormFu::Filter::ForceListValue> to address a problem with
587             L<increment_field_names> disabled, and increading the L<repeat> on the
588             server-side.
589              
590             =head1 SEE ALSO
591              
592             Is a sub-class of, and inherits methods from
593             L<HTML::FormFu::Element::Block>,
594             L<HTML::FormFu::Element>
595              
596             L<HTML::FormFu>
597              
598             =head1 AUTHOR
599              
600             Carl Franks, C<cfranks@cpan.org>
601              
602             =head1 LICENSE
603              
604             This library is free software, you can redistribute it and/or modify it under
605             the same terms as Perl itself.
606              
607             =cut