package HTML::FormFu::Element::Repeatable;
use strict;
use base 'HTML::FormFu::Element::Block';
use Class::C3;
use Carp qw/ croak /;
__PACKAGE__->mk_accessors(
qw/ _original_elements increment_field_names counter_name /);
sub new {
my $self = shift->next::method(@_);
$self->filename('repeatable');
$self->is_repeatable(1);
$self->increment_field_names(1);
return $self;
}
sub repeat {
my ( $self, $count ) = @_;
$count ||= 1;
croak "invalid number to repeat"
unless $count =~ /^[1-9][0-9]*\z/;
my $children;
if ( $self->_original_elements ) {
# repeat() has already been called
$children = $self->_original_elements;
}
else {
$children = $self->_elements;
$self->_original_elements($children);
}
croak "no child elements to repeat"
if !@$children;
$self->_elements( [] );
my @return;
for my $rep ( 1 .. $count ) {
my @clones = map { $_->clone } @$children;
my $block = $self->element('Block');
$block->_elements( \@clones );
$block->attributes( $self->attributes );
$block->tag( $self->tag );
$block->repeatable_count($rep);
if ( $self->increment_field_names ) {
for my $field ( @{ $block->get_fields } ) {
if ( defined( my $name = $field->name ) ) {
$field->original_name($name);
$name .= "_$rep";
$field->name($name);
}
}
}
_reparent_children($block);
for my $field ( @{ $block->get_fields } ) {
map { $_->parent($field) }
@{ $field->_deflators },
@{ $field->_filters },
@{ $field->_constraints },
@{ $field->_inflators },
@{ $field->_validators },
@{ $field->_transformers };
}
my $block_fields = $block->get_fields;
my @others_constraints = grep { $_->can('others') }
map { @{ $_->_constraints } } @$block_fields;
for my $constraint (@others_constraints) {
my $others = $constraint->others;
$others = [$others] if !ref $others;
my @new_others;
for my $name (@$others) {
my ($field)
= grep { $_->original_name eq $name } @$block_fields;
if ( defined $field ) {
push @new_others, $field->nested_name;
}
else {
push @new_others, $name;
}
}
$constraint->others( \@new_others );
}
push @return, $block;
}
return \@return;
}
sub _reparent_children {
my $self = shift;
return if $self->is_field;
for my $child ( @{ $self->get_elements } ) {
$child->parent($self);
_reparent_children($child);
}
}
sub process {
my $self = shift;
my $form = $self->form;
my $count = 1;
if ( defined $self->counter_name && defined $form->query ) {
my $input = $form->query->param( $self->counter_name );
$count = $input
if defined $input && $input =~ /^[1-9][0-9]*\z/;
}
unless ( $self->_original_elements ) {
$self->repeat($count);
}
return $self->next::method(@_);
}
sub content {
my $self = shift;
croak "Repeatable elements do not support the content() method"
if @_;
return;
}
sub string {
my ( $self, $args ) = @_;
$args ||= {};
my $render
= exists $args->{render_data}
? $args->{render_data}
: $self->render_data_non_recursive;
# block template
my @divs = map { $_->render } @{ $self->get_elements };
my $html = join "\n", @divs;
return $html;
}
1;
__END__
=head1 NAME
HTML::FormFu::Element::Repeatable - repeatable block element
=head1 SYNOPSIS
---
elements:
- type: Repeatable
elements:
- name: foo
- name: bar
Calling C<< $element->repeat(2) >> would result in the following markup:
=head1 DESCRIPTION
Provides a way to extend a form at run-time, by copying and repeating its
child elements.
The elements intended for copying must be added before L is called.
Although the Repeatable element inherits from
L, it doesn't generate a block tag
around all the repeated elements - instead it places each repeat of the
elements in a new L element, which
inherits the Repeatable's display settings, such as L and
L.
=head1 METHODS
=head2 repeat
Arguments: [$count]
Return Value: $arrayref_of_new_child_blocks
This method creates C<$count> number of copies of the child elements.
If no argument C<$count> is provided, it defaults to C<1>.
Note that C<< $form->process >> will call L automatically to ensure the
initial child elements are correctly set up - unless you call L
manually first, in which case the child elements you created will be left
untouched (otherwise L would overwrite your changes).
Any subsequent call to L will delete the previously copied elements
before creating new copies - this means you cannot make repeated calls to
L within a loop to create more copies.
Each copy of the elements returned are contained in a new
L element. For example, calling
C<< $element->repeat(2) >> on a Repeatable element containing 2 Text fields
would return 2 L elements, each
containing a copy of the 2 Text fields.
=head2 counter_name
Arguments: $name
If true, the L will be searched during
L for a parameter with the given name. The value for
that parameter will be passed to L, to automatically create the
new copies.
If L is true (the default), this is essential: if the
elements corresponding to the new fieldnames (foo_1, bar_2, etc.) are not
present on the form during L, no Processors
(Constraints, etc.) will be run on the fields, and their values will not
be returned by L or L.
=head2 increment_field_names
Arguments: $bool
Default Value: 1
If true, then any copies of fields whose name contains a C<0>, will have
the C<0> replaced by it's L value.
---
elements:
- type: Repeatable
increment_field_names: 1
elements:
- name: foo_0
- name: bar_0
Calling C<< $element->repeat(2) >> would result in the following markup:
See also L.
=head2 repeatable_count
This is set on each new L element
returned by L, starting at number C<1>.
Because this is an 'inherited accessor' available on all elements, it can be
used to determine whether any element is a child of a Repeatable element.
=head2 attributes
=head2 attrs
Any attributes set will be passed to every repeated Block of elements.
---
elements:
- type: Repeatable
attributes:
class: rep
elements:
- name: foo
Calling C<< $element->repeat(2) >> would result in the following markup:
See L for details.
=head2 tag
The L value will be passed to every repeated Block of elements.
---
elements:
- type: Repeatable
tag: span
elements:
- name: foo
Calling C<< $element->repeat(2) >> would result in the following markup:
See L for details.
=head2 auto_id
As well as the usual subtitutions, any instances of C<%r> will be
replaced with the value of L.
See L for further details.
---
elements:
- type: Repeatable
auto_id: "%n_%r"
elements:
- name: foo
Calling C<< $element->repeat(2) >> would result in the following markup:
=head2 content
Not supported for Repeatable elements - will throw a fatal error if called as
a setter.
=head1 SEE ALSO
Is a sub-class of, and inherits methods from
L,
L
L
=head1 AUTHOR
Carl Franks, C
=head1 LICENSE
This library is free software, you can redistribute it and/or modify it under
the same terms as Perl itself.