package HTML::FormFu::Element::_Group; use strict; use base 'HTML::FormFu::Element::_Field'; use Class::C3; use HTML::FormFu::ObjectUtil qw/ _coerce /; use HTML::FormFu::Util qw/ append_xml_attribute literal xml_escape /; use HTML::FormFu::Attribute qw/ mk_output_accessors /; use Storable qw( dclone ); use Carp qw( croak ); __PACKAGE__->mk_accessors(qw/ _options empty_first /); __PACKAGE__->mk_output_accessors(qw/ empty_first_label/); my @ALLOWED_OPTION_KEYS = qw/ group value value_xml value_loc label label_xml label_loc attributes attrs attributes_xml attrs_xml label_attributes label_attrs label_attributes_xml label_attrs_xml /; sub new { my $self = shift->next::method(@_); $self->_options( [] ); $self->container_attributes( {} ); return $self; } sub process { my $self = shift; $self->next::method(@_); my $args = $self->model_config; return unless $args && keys %$args; return if @{ $self->options }; # don't run if {options_from_model} is set and is 0 my $option_flag = exists $args->{options_from_model} ? $args->{options_from_model} : 1; return if !$option_flag; $self->options( [ $self->form->model->options_from_model( $self, $args ) ] ); return; } sub options { my ( $self, $arg ) = @_; my ( @options, @new ); return $self->_options if @_ == 1; croak "options argument must be a single array-ref" if @_ > 2; if ( defined $arg ) { eval { @options = @$arg }; croak "options argument must be an array-ref" if $@; if ( $self->empty_first ) { push @new, $self->_get_empty_first_option; } for my $item (@options) { push @new, $self->_parse_option($item); } } $self->_options( \@new ); return $self; } sub _get_empty_first_option { my ($self) = @_; my $l = $self->empty_first_label || ''; return { value => '', label => $l, attributes => {}, label_attributes => {}, }; } sub _parse_option { my ( $self, $item ) = @_; eval { my %x = %$item }; if ( !$@ ) { # was passed a hashref return $self->_parse_option_hashref($item); } eval { my @x = @$item }; if ( !$@ ) { # was passed an arrayref return { value => $item->[0], label => $item->[1], attributes => {}, label_attributes => {}, }; } croak "each options argument must be a hash-ref or array-ref"; } sub _parse_option_hashref { my ( $self, $item ) = @_; # sanity check options my @keys = keys %$item; for my $key (@keys) { croak "unknown option argument: '$key'" if !grep { $key eq $_ } @ALLOWED_OPTION_KEYS; my $short = $key; if ( $short =~ s/attributes/attrs/ ) { for my $cmp (@keys) { next if $cmp eq $key; croak "cannot use both '$key' and '$short' arguments" if $cmp eq $short; } } } if ( exists $item->{group} ) { my @group = @{ $item->{group} }; my @new; for my $groupitem (@group) { push @new, $self->_parse_option($groupitem); } $item->{group} = \@new; } if ( !exists $item->{attributes} ) { $item->{attributes} = exists $item->{attrs} ? $item->{attrs} : {}; } if ( exists $item->{attributes_xml} ) { for my $key ( keys %{ $item->{attributes_xml} } ) { $item->{attributes}{$key} = literal( $item->{attributes_xml}{$key} ); } } elsif ( exists $item->{attrs_xml} ) { for my $key ( keys %{ $item->{attrs_xml} } ) { $item->{attributes}{$key} = literal( $item->{attrs_xml}{$key} ); } } if ( !exists $item->{label_attributes} ) { $item->{label_attributes} = exists $item->{label_attrs} ? $item->{label_attrs} : {}; } if ( exists $item->{label_attributes_xml} ) { for my $key ( keys %{ $item->{label_attributes_xml} } ) { $item->{label_attributes}{$key} = literal( $item->{label_attributes_xml}{$key} ); } } elsif ( exists $item->{label_attrs_xml} ) { for my $key ( keys %{ $item->{label_attrs_xml} } ) { $item->{label_attributes}{$key} = literal( $item->{label_attrs_xml}{$key} ); } } if ( defined $item->{label_xml} ) { $item->{label} = literal( $item->{label_xml} ); } elsif ( defined $item->{label_loc} ) { $item->{label} = $self->form->localize( $item->{label_loc} ); } if ( defined $item->{value_xml} ) { $item->{value} = literal( $item->{value_xml} ); } elsif ( defined $item->{value_loc} ) { $item->{value} = $self->form->localize( $item->{value_loc} ); } $item->{value} = '' if !defined $item->{value}; return $item; } sub values { my ( $self, $arg ) = @_; my ( @values, @new ); croak "values argument must be a single array-ref of values" if @_ > 2; if ( defined $arg ) { eval { @values = @$arg }; croak "values argument must be an array-ref" if $@; } @new = ( map { { value => $_, label => ucfirst $_, attributes => {}, label_attributes => {}, } } @values ); if ( $self->empty_first ) { unshift @new, $self->_get_empty_first_option; } $self->_options( \@new ); return $self; } sub value_range { my ( $self, $arg ) = @_; my (@values); croak "value_range argument must be a single array-ref of values" if @_ > 2; if ( defined $arg ) { eval { @values = @$arg }; croak "value_range argument must be an array-ref" if $@; } croak "range must contain at least 2 values" if @$arg < 2; my $end = pop @values; my $start = pop @values; return $self->values( [ @values, $start .. $end ] ); } sub prepare_attrs { my ( $self, $render ) = @_; my $submitted = $self->form->submitted; my $default = $self->default; my $value = defined $self->name ? $self->get_nested_hash_value( $self->form->input, $self->nested_name ) : undef; for my $option ( @{ $render->{options} } ) { if ( exists $option->{group} ) { for my $item ( @{ $option->{group} } ) { $self->_prepare_attrs( $submitted, $value, $default, $item ); } } else { $self->_prepare_attrs( $submitted, $value, $default, $option ); } } $self->next::method($render); return; } sub render_data_non_recursive { my $self = shift; my $render = $self->next::method( { options => dclone( $self->_options ), @_ ? %{ $_[0] } : () } ); $self->_quote_options( $render->{options} ); return $render; } sub _quote_options { my ( $self, $options ) = @_; foreach my $opt (@$options) { $opt->{label} = xml_escape( $opt->{label} ); $opt->{value} = xml_escape( $opt->{value} ); $self->_quote_options( $opt->{group} ) if exists $opt->{group}; } } sub string { my ( $self, $args ) = @_; $args ||= {}; my $render = exists $args->{render_data} ? $args->{render_data} : $self->render_data; # field wrapper template - start my $html = $self->_string_field_start($render); # input_tag template $html .= $self->_string_field($render); # field wrapper template - end $html .= $self->_string_field_end($render); return $html; } sub as { my ( $self, $type, %attrs ) = @_; return $self->_coerce( type => $type, attributes => \%attrs, errors => $self->_errors, package => __PACKAGE__, ); } sub clone { my $self = shift; my $clone = $self->next::method(@_); $clone->_options( dclone $self->_options ); return $clone; } 1; __END__ =head1 NAME HTML::FormFu::Element::_Group - grouped form field base-class =head1 DESCRIPTION Base class for L and L fields. =head1 METHODS =head2 options Arguments: none Arguments: \@options --- elements: - type: Select name: foo options: - [ 01, January ] - [ 02, February ] - value: 03 label: March attributes: style: highlighted - [ 04, April ] If passed no arguments, it returns an arrayref of the currently set options. Use to set the list of items in the select menu / radiogroup. It's arguments must be an array-ref of items. Each item may be an array ref of the form C<[ $value, $label ]> or a hash-ref of the form C<< { value => $value, label => $label } >>. Each hash-ref may also have the keys C and C. Passing an item containing a C key will, for L