File Coverage

blib/lib/HTML/FormFu/Role/Element/Group.pm
Criterion Covered Total %
statement 118 132 89.3
branch 58 76 76.3
condition 2 2 100.0
subroutine 17 17 100.0
pod n/a
total 195 227 85.9


line stmt bran cond sub pod time code
1             package HTML::FormFu::Role::Element::Group;
2              
3 68     68   39902 use strict;
  68         104  
  68         2858  
4             our $VERSION = '2.05'; # VERSION
5              
6 68     68   263 use Moose::Role;
  68         90  
  68         525  
7 68     68   241727 use MooseX::Attribute::FormFuChained;
  68         102  
  68         2416  
8              
9             with 'HTML::FormFu::Role::Element::Field',
10             'HTML::FormFu::Role::Element::SingleValueField',
11             'HTML::FormFu::Role::Element::ProcessOptionsFromModel',
12             'HTML::FormFu::Role::Element::Coercible';
13              
14 68     68   258 use HTML::FormFu::Attribute qw( mk_output_accessors );
  68         86  
  68         3844  
15 68     68   280 use HTML::FormFu::Util qw( append_xml_attribute literal xml_escape );
  68         91  
  68         2920  
16 68     68   266 use Clone ();
  68         98  
  68         1681  
17 68     68   234 use List::Util 1.33 qw( none );
  68         1544  
  68         3339  
18 68     68   637 use Scalar::Util qw( reftype );
  68         96  
  68         2594  
19 68     68   269 use Carp qw( croak );
  68         88  
  68         101396  
20              
21             has empty_first => ( is => 'rw', traits => ['FormFuChained'] );
22              
23             __PACKAGE__->mk_output_accessors(qw( empty_first_label ));
24              
25             has _options => (
26             is => 'rw',
27             default => sub { [] },
28             lazy => 1,
29             isa => 'ArrayRef',
30             );
31              
32             my @ALLOWED_OPTION_KEYS = qw(
33             group
34             value
35             value_xml
36             value_loc
37             label
38             label_xml
39             label_loc
40             attributes
41             attrs
42             attributes_xml
43             attrs_xml
44             container_attributes
45             container_attrs
46             container_attributes_xml
47             container_attrs_xml
48             label_attributes
49             label_attrs
50             label_attributes_xml
51             label_attrs_xml
52             );
53              
54             after BUILD => sub {
55             my $self = shift;
56              
57             $self->container_attributes( {} );
58              
59             return;
60             };
61              
62             after process => sub {
63             my $self = shift;
64              
65             $self->_process_options_from_model;
66              
67             return;
68             };
69              
70             sub options {
71 239     239   2287 my ( $self, $arg ) = @_;
72 239         320 my ( @options, @new );
73              
74 239 100       1203 return $self->_options if @_ == 1;
75              
76 214 50       504 croak "options argument must be a single array-ref" if @_ > 2;
77              
78 214 50       529 if ( defined $arg ) {
79 214 50       784 croak "options argument must be an array-ref"
80             if reftype($arg) ne 'ARRAY';
81              
82 214         619 @options = @$arg;
83              
84 214 100       6313 if ( $self->empty_first ) {
85 6         23 push @new, $self->_get_empty_first_option;
86             }
87              
88 214         419 for my $item (@options) {
89 3848         4152 push @new, $self->_parse_option($item);
90             }
91             }
92              
93 212         6173 $self->_options( \@new );
94              
95 212         765 return $self;
96             }
97              
98             sub _get_empty_first_option {
99 20     20   28 my ($self) = @_;
100              
101 20   100     81 my $label = $self->empty_first_label || '';
102              
103             return {
104 20         109 value => '',
105             label => $label,
106             attributes => {},
107             container_attributes => {},
108             label_attributes => {},
109             };
110             }
111              
112             sub _parse_option {
113 3870     3870   2658 my ( $self, $item ) = @_;
114              
115 3870 100       8082 if ( reftype($item) eq 'HASH' ) {
    50          
116 38         118 return $self->_parse_option_hashref($item);
117             }
118             elsif ( reftype($item) eq 'ARRAY' ) {
119             return {
120 3832         11368 value => $item->[0],
121             label => $item->[1],
122             attributes => {},
123             container_attributes => {},
124             label_attributes => {},
125             };
126             }
127             else {
128 0         0 croak "each options argument must be a hash-ref or array-ref";
129             }
130             }
131              
132             sub _parse_option_hashref {
133 38     38   39 my ( $self, $item ) = @_;
134              
135             # sanity check options
136 38         119 my @keys = keys %$item;
137              
138 38         59 for my $key (@keys) {
139             croak "unknown option argument: '$key'"
140 85 100   437   242 if none { $key eq $_ } @ALLOWED_OPTION_KEYS;
  437         520  
141              
142 84         135 my $short = $key;
143              
144 84 100       198 if ( $short =~ s/attributes/attrs/ ) {
145 20         26 for my $cmp (@keys) {
146 76 100       117 next if $cmp eq $key;
147              
148 56 100       239 croak "cannot use both '$key' and '$short' arguments"
149             if $cmp eq $short;
150             }
151             }
152             }
153              
154 36 100       77 if ( exists $item->{group} ) {
155 11         13 my @group = @{ $item->{group} };
  11         20  
156 11         12 my @new;
157 11         14 for my $groupitem (@group) {
158 22         38 push @new, $self->_parse_option($groupitem);
159             }
160 11         21 $item->{group} = \@new;
161             }
162              
163 36 100       98 if ( !exists $item->{attributes} ) {
164             $item->{attributes}
165             = exists $item->{attrs}
166             ? $item->{attrs}
167 26 100       78 : {};
168             }
169              
170 36 100       101 if ( exists $item->{attributes_xml} ) {
    100          
171 3         5 for my $key ( keys %{ $item->{attributes_xml} } ) {
  3         12  
172             $item->{attributes}{$key}
173 3         14 = literal( $item->{attributes_xml}{$key} );
174             }
175             }
176             elsif ( exists $item->{attrs_xml} ) {
177 1         1 for my $key ( keys %{ $item->{attrs_xml} } ) {
  1         4  
178 1         6 $item->{attributes}{$key} = literal( $item->{attrs_xml}{$key} );
179             }
180             }
181              
182 36 100       68 if ( !exists $item->{container_attributes} ) {
183             $item->{container_attributes}
184             = exists $item->{container_attrs}
185             ? $item->{container_attrs}
186 32 50       71 : {};
187             }
188              
189 36 50       105 if ( exists $item->{container_attributes_xml} ) {
    50          
190 0         0 for my $key ( keys %{ $item->{container_attributes_xml} } ) {
  0         0  
191             $item->{container_attributes}{$key}
192 0         0 = literal( $item->{container_attributes_xml}{$key} );
193             }
194             }
195             elsif ( exists $item->{container_attrs_xml} ) {
196 0         0 for my $key ( keys %{ $item->{container_attrs_xml} } ) {
  0         0  
197             $item->{container_attributes}{$key}
198 0         0 = literal( $item->{container_attrs_xml}{$key} );
199             }
200             }
201              
202 36 100       73 if ( !exists $item->{label_attributes} ) {
203             $item->{label_attributes}
204             = exists $item->{label_attrs}
205             ? $item->{label_attrs}
206 34 50       88 : {};
207             }
208              
209 36 50       95 if ( exists $item->{label_attributes_xml} ) {
    50          
210 0         0 for my $key ( keys %{ $item->{label_attributes_xml} } ) {
  0         0  
211             $item->{label_attributes}{$key}
212 0         0 = literal( $item->{label_attributes_xml}{$key} );
213             }
214             }
215             elsif ( exists $item->{label_attrs_xml} ) {
216 0         0 for my $key ( keys %{ $item->{label_attrs_xml} } ) {
  0         0  
217             $item->{label_attributes}{$key}
218 0         0 = literal( $item->{label_attrs_xml}{$key} );
219             }
220             }
221              
222 36 100       113 if ( defined $item->{label_xml} ) {
    100          
223 2         6 $item->{label} = literal( $item->{label_xml} );
224             }
225             elsif ( defined $item->{label_loc} ) {
226 1         8 $item->{label} = $self->form->localize( $item->{label_loc} );
227             }
228              
229 36 100       94 if ( defined $item->{value_xml} ) {
    50          
230 2         4 $item->{value} = literal( $item->{value_xml} );
231             }
232             elsif ( defined $item->{value_loc} ) {
233 0         0 $item->{value} = $self->form->localize( $item->{value_loc} );
234             }
235              
236 36 100       68 if ( !defined $item->{value} ) {
237 12         19 $item->{value} = '';
238             }
239              
240 36         85 return $item;
241             }
242              
243             sub values {
244 55     55   94 my ( $self, $arg ) = @_;
245              
246 55 50       146 croak "values argument must be a single array-ref of values" if @_ > 2;
247              
248 55         69 my @values;
249              
250 55 50       134 if ( defined $arg ) {
251 55 50       218 croak "values argument must be an array-ref"
252             if reftype($arg) ne 'ARRAY';
253              
254 55         151 @values = @$arg;
255             }
256              
257 55         89 my @new = map { {
258 165         728 value => $_,
259             label => ucfirst $_,
260             attributes => {},
261             container_attributes => {},
262             label_attributes => {},
263             }
264             } @values;
265              
266 55 100       1563 if ( $self->empty_first ) {
267 14         52 unshift @new, $self->_get_empty_first_option;
268             }
269              
270 55         1429 $self->_options( \@new );
271              
272 55         226 return $self;
273             }
274              
275             sub value_range {
276 3     3   9 my ( $self, $arg ) = @_;
277 3         3 my (@values);
278              
279 3 50       8 croak "value_range argument must be a single array-ref of values"
280             if @_ > 2;
281              
282 3 50       8 if ( defined $arg ) {
283 3 50       11 croak "value_range argument must be an array-ref"
284             if reftype($arg) ne 'ARRAY';
285              
286 3         7 @values = @$arg;
287             }
288              
289 3 50       6 croak "range must contain at least 2 values" if @values < 2;
290              
291 3         5 my $end = pop @values;
292 3         4 my $start = pop @values;
293              
294 3         36 return $self->values( [ @values, $start .. $end ] );
295             }
296              
297             before prepare_attrs => sub {
298             my ( $self, $render ) = @_;
299              
300             my $submitted = $self->form->submitted;
301             my $default = $self->default;
302              
303             my $value
304             = defined $self->name
305             ? $self->get_nested_hash_value( $self->form->input, $self->nested_name )
306             : undef;
307              
308             if ( ( reftype($value) || '' ) eq 'ARRAY' ) {
309             my $elems
310             = $self->form->get_fields( { nested_name => $self->nested_name } );
311             if ($#$elems) {
312              
313             # There are multiple fields with the same name; assume
314             # none are multi-value fields, i.e. only one selected
315             # option per field. (Otherwise it might be ambiguous
316             # which option came from which field.)
317             for ( 0 .. @$elems - 1 ) {
318             if ( $self == $elems->[$_] ) {
319              
320             # Use the value of the option actually selected in
321             # this group.
322             $value = $value->[$_];
323             }
324             }
325             }
326             }
327              
328             if ( !$submitted && defined $default ) {
329             for my $deflator ( @{ $self->_deflators } ) {
330             $default = $deflator->process($default);
331             }
332             }
333              
334             for my $option ( @{ $render->{options} } ) {
335             if ( exists $option->{group} ) {
336             for my $item ( @{ $option->{group} } ) {
337             $self->_prepare_attrs( $submitted, $value, $default, $item );
338             }
339             }
340             else {
341             $self->_prepare_attrs( $submitted, $value, $default, $option );
342             }
343             }
344              
345             return;
346             };
347              
348             around render_data_non_recursive => sub {
349             my ( $orig, $self, $args ) = @_;
350              
351             my $render = $self->$orig( {
352             options => Clone::clone( $self->_options ),
353             $args ? %$args : (),
354             } );
355              
356             $self->_quote_options( $render->{options} );
357              
358             return $render;
359             };
360              
361             sub _quote_options {
362 504     504   583 my ( $self, $options ) = @_;
363              
364 504         723 foreach my $opt (@$options) {
365 8180         11471 $opt->{label} = xml_escape( $opt->{label} );
366 8180         11107 $opt->{value} = xml_escape( $opt->{value} );
367 8180         10951 $opt->{attributes} = xml_escape( $opt->{attributes} );
368 8180         12378 $opt->{label_attributes} = xml_escape( $opt->{label_attributes} );
369             $opt->{container_attributes}
370 8180         12208 = xml_escape( $opt->{container_attributes} );
371              
372 8180 100       12764 if ( exists $opt->{group} ) {
373 29         56 $self->_quote_options( $opt->{group} );
374             }
375             }
376             }
377              
378             around clone => sub {
379             my ( $orig, $self ) = @_;
380              
381             my $clone = $self->$orig(@_);
382              
383             $clone->_options( Clone::clone( $self->_options ) );
384              
385             return $clone;
386             };
387              
388             1;
389              
390             __END__
391              
392             =head1 NAME
393              
394             HTML::FormFu::Role::Element::Group - Role for grouped form fields
395              
396             =head1 VERSION
397              
398             version 2.05
399              
400             =head1 DESCRIPTION
401              
402             Base class for L<HTML::FormFu::Element::Checkboxgroup>,
403             L<HTML::FormFu::Element::Radiogroup>, and
404             L<HTML::FormFu::Element::Select> fields.
405              
406             =head1 METHODS
407              
408             =head2 options
409              
410             Arguments: none
411              
412             Arguments: \@options
413              
414             ---
415             elements:
416             - type: Select
417             name: foo
418             options:
419             - [ 01, January ]
420             - [ 02, February ]
421             - value: 03
422             label: March
423             attributes:
424             style: highlighted
425             - [ 04, April ]
426              
427             If passed no arguments, it returns an arrayref of the currently set options.
428              
429             Use to set the list of items in the select menu / radiogroup.
430              
431             Its arguments must be an array-ref of items. Each item may be an array ref
432             of the form C<[ $value, $label ]> or a hash-ref of the form
433             C<< { value => $value, label => $label } >>. Each hash-ref may also have an
434             C<attributes> key.
435              
436             Passing an item containing a C<group> key will, for
437             L<Select fields|HTML::FormFu::Element::Select>, create an optgroup. And for
438             L<Radiogroup fields|HTML::FormFu::Element::Radiogroup> or
439             L<Checkboxgroup fields|HTML::FormFu::Element::Checkboxgroup>, create a
440             sub-group of radiobuttons or checkboxes with a new C<span> block, with the
441             classname C<subgroup>.
442              
443             An example of Select optgroups:
444              
445             ---
446             elements:
447             - type: Select
448             name: foo
449             options:
450             - label: "group 1"
451             group:
452             - [1a, 'item 1a']
453             - [1b, 'item 1b']
454             - label: "group 2"
455             group:
456             - [2a, 'item 2a']
457             - [2b, 'item 2b']
458              
459             When using the hash-ref construct, the C<label_xml> and C<label_loc>
460             variants of C<label> are supported, as are the C<value_xml> and C<value_loc>
461             variants of C<value>, the C<attributes_xml> variant of C<attributes> and the
462             C<label_attributes_xml> variant of C<label_attributes>.
463              
464             C<container_attributes> or C<container_attributes_xml> is used by
465             L<HTML::FormFu::Element::Checkboxgroup> and
466             L<HTML::FormFu::Element::Radiogroup> for the c<span> surrounding each
467             item's input and label. It is ignored by L<HTML::FormFu::Element::Select>
468             elements.
469              
470             C<label_attributes> / C<label_attributes_xml> is used by
471             L<HTML::FormFu::Element::Checkboxgroup> and
472             L<HTML::FormFu::Element::Radiogroup> for the c<label> tag of each item.
473             It is ignored by L<HTML::FormFu::Element::Select> elements.
474              
475             =head2 values
476              
477             Arguments: \@values
478              
479             ---
480             elements:
481             - type: Radiogroup
482             name: foo
483             values:
484             - jan
485             - feb
486             - mar
487             - apr
488              
489             A more concise alternative to L</options>. Use to set the list of values in
490             the select menu / radiogroup.
491              
492             Its arguments must be an array-ref of values. The labels used are the
493             result of C<ucfirst($value)>.
494              
495             =head2 value_range
496              
497             Arguments: \@values
498              
499             ---
500             elements:
501             - type: Select
502             name: foo
503             value_range:
504             - ""
505             - 1
506             - 12
507              
508             Similar to L</values>, but the last 2 values are expanded to a range. Any
509             preceding values are used literally, allowing the common empty first item
510             in select menus.
511              
512             =head2 empty_first
513              
514             If true, then a blank option will be inserted at the start of the option list
515             (regardless of whether L</options>, L</values> or L</value_range> was used to
516             populate the options). See also L</empty_first_label>.
517              
518             =head2 empty_first_label
519              
520             =head2 empty_first_label_xml
521              
522             =head2 empty_first_label_loc
523              
524             If L</empty_first> is true, and L</empty_first_label> is set, this value will
525             be used as the label for the first option - so only the first option's value
526             will be empty.
527              
528             =head1 SEE ALSO
529              
530             Is a sub-class of, and inherits methods from
531             L<HTML::FormFu::Role::Element::Field>, L<HTML::FormFu::Element>
532              
533             L<HTML::FormFu>
534              
535             =head1 AUTHOR
536              
537             Carl Franks, C<cfranks@cpan.org>
538              
539             =head1 LICENSE
540              
541             This library is free software, you can redistribute it and/or modify it under
542             the same terms as Perl itself.
543              
544             =cut