File Coverage

blib/lib/HTML/FormFu/Role/Element/Group.pm
Criterion Covered Total %
statement 115 129 89.1
branch 58 76 76.3
condition 2 2 100.0
subroutine 16 16 100.0
pod n/a
total 191 223 85.6


line stmt bran cond sub pod time code
1 69     69   49767 use strict;
  69         183  
  69         12713  
2              
3             package HTML::FormFu::Role::Element::Group;
4             $HTML::FormFu::Role::Element::Group::VERSION = '2.07';
5             # ABSTRACT: Role for grouped form fields
6              
7 69     69   463 use Moose::Role;
  69         157  
  69         699  
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 69     69   378407 use HTML::FormFu::Attribute qw( mk_output_accessors );
  69         188  
  69         4524  
15 69     69   478 use HTML::FormFu::Util qw( append_xml_attribute literal xml_escape );
  69         162  
  69         4298  
16 69     69   504 use Clone ();
  69         159  
  69         2264  
17 69     69   436 use List::Util 1.33 qw( none );
  69         1968  
  69         4363  
18 69     69   484 use Scalar::Util qw( reftype );
  69         177  
  69         3697  
19 69     69   474 use Carp qw( croak );
  69         164  
  69         142009  
20              
21             has empty_first => ( is => 'rw', traits => ['Chained'] );
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 241     241   4378 my ( $self, $arg ) = @_;
72 241         547 my ( @options, @new );
73              
74 241 100       1482 return $self->_options if @_ == 1;
75              
76 216 50       641 croak "options argument must be a single array-ref" if @_ > 2;
77              
78 216 50       668 if ( defined $arg ) {
79 216 50       1030 croak "options argument must be an array-ref"
80             if reftype($arg) ne 'ARRAY';
81              
82 216         817 @options = @$arg;
83              
84 216 100       7715 if ( $self->empty_first ) {
85 6         31 push @new, $self->_get_empty_first_option;
86             }
87              
88 216         651 for my $item (@options) {
89 3852         7600 push @new, $self->_parse_option($item);
90             }
91             }
92              
93 214         7685 $self->_options( \@new );
94              
95 214         1009 return $self;
96             }
97              
98             sub _get_empty_first_option {
99 20     20   51 my ($self) = @_;
100              
101 20   100     117 my $label = $self->empty_first_label || '';
102              
103             return {
104 20         194 value => '',
105             label => $label,
106             attributes => {},
107             container_attributes => {},
108             label_attributes => {},
109             };
110             }
111              
112             sub _parse_option {
113 3874     3874   6251 my ( $self, $item ) = @_;
114              
115 3874 100       10253 if ( reftype($item) eq 'HASH' ) {
    50          
116 38         207 return $self->_parse_option_hashref($item);
117             }
118             elsif ( reftype($item) eq 'ARRAY' ) {
119             return {
120 3836         17205 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   124 my ( $self, $item ) = @_;
134              
135             # sanity check options
136 38         145 my @keys = keys %$item;
137              
138 38         117 for my $key (@keys) {
139             croak "unknown option argument: '$key'"
140 86 100   446   426 if none { $key eq $_ } @ALLOWED_OPTION_KEYS;
  446         917  
141              
142 85         244 my $short = $key;
143              
144 85 100       326 if ( $short =~ s/attributes/attrs/ ) {
145 20         47 for my $cmp (@keys) {
146 74 100       160 next if $cmp eq $key;
147              
148 55 100       316 croak "cannot use both '$key' and '$short' arguments"
149             if $cmp eq $short;
150             }
151             }
152             }
153              
154 36 100       117 if ( exists $item->{group} ) {
155 11         19 my @group = @{ $item->{group} };
  11         34  
156 11         20 my @new;
157 11         21 for my $groupitem (@group) {
158 22         64 push @new, $self->_parse_option($groupitem);
159             }
160 11         33 $item->{group} = \@new;
161             }
162              
163 36 100       148 if ( !exists $item->{attributes} ) {
164             $item->{attributes}
165             = exists $item->{attrs}
166             ? $item->{attrs}
167 26 100       122 : {};
168             }
169              
170 36 100       135 if ( exists $item->{attributes_xml} ) {
    100          
171 3         8 for my $key ( keys %{ $item->{attributes_xml} } ) {
  3         18  
172             $item->{attributes}{$key}
173 3         22 = literal( $item->{attributes_xml}{$key} );
174             }
175             }
176             elsif ( exists $item->{attrs_xml} ) {
177 1         2 for my $key ( keys %{ $item->{attrs_xml} } ) {
  1         6  
178 1         8 $item->{attributes}{$key} = literal( $item->{attrs_xml}{$key} );
179             }
180             }
181              
182 36 100       99 if ( !exists $item->{container_attributes} ) {
183             $item->{container_attributes}
184             = exists $item->{container_attrs}
185             ? $item->{container_attrs}
186 32 50       115 : {};
187             }
188              
189 36 50       143 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       113 if ( !exists $item->{label_attributes} ) {
203             $item->{label_attributes}
204             = exists $item->{label_attrs}
205             ? $item->{label_attrs}
206 34 50       113 : {};
207             }
208              
209 36 50       167 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       159 if ( defined $item->{label_xml} ) {
    100          
223 2         9 $item->{label} = literal( $item->{label_xml} );
224             }
225             elsif ( defined $item->{label_loc} ) {
226 1         12 $item->{label} = $self->form->localize( $item->{label_loc} );
227             }
228              
229 36 100       141 if ( defined $item->{value_xml} ) {
    50          
230 2         8 $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       105 if ( !defined $item->{value} ) {
237 12         37 $item->{value} = '';
238             }
239              
240 36         144 return $item;
241             }
242              
243             sub values {
244 55     55   180 my ( $self, $arg ) = @_;
245              
246 55 50       192 croak "values argument must be a single array-ref of values" if @_ > 2;
247              
248 55         121 my @values;
249              
250 55 50       202 if ( defined $arg ) {
251 55 50       314 croak "values argument must be an array-ref"
252             if reftype($arg) ne 'ARRAY';
253              
254 55         208 @values = @$arg;
255             }
256              
257             my @new = map {
258 55         140 { value => $_,
  165         1026  
259             label => ucfirst $_,
260             attributes => {},
261             container_attributes => {},
262             label_attributes => {},
263             }
264             } @values;
265              
266 55 100       1993 if ( $self->empty_first ) {
267 14         63 unshift @new, $self->_get_empty_first_option;
268             }
269              
270 55         1877 $self->_options( \@new );
271              
272 55         444 return $self;
273             }
274              
275             sub value_range {
276 3     3   13 my ( $self, $arg ) = @_;
277 3         7 my (@values);
278              
279 3 50       10 croak "value_range argument must be a single array-ref of values"
280             if @_ > 2;
281              
282 3 50       9 if ( defined $arg ) {
283 3 50       15 croak "value_range argument must be an array-ref"
284             if reftype($arg) ne 'ARRAY';
285              
286 3         9 @values = @$arg;
287             }
288              
289 3 50       10 croak "range must contain at least 2 values" if @values < 2;
290              
291 3         6 my $end = pop @values;
292 3         14 my $start = pop @values;
293              
294 3         15 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 506     506   1205 my ( $self, $options ) = @_;
363              
364 506         1292 foreach my $opt (@$options) {
365 8184         15959 $opt->{label} = xml_escape( $opt->{label} );
366 8184         16297 $opt->{value} = xml_escape( $opt->{value} );
367 8184         16079 $opt->{attributes} = xml_escape( $opt->{attributes} );
368 8184         16120 $opt->{label_attributes} = xml_escape( $opt->{label_attributes} );
369             $opt->{container_attributes}
370 8184         15951 = xml_escape( $opt->{container_attributes} );
371              
372 8184 100       17788 if ( exists $opt->{group} ) {
373 29         69 $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             =pod
393              
394             =encoding UTF-8
395              
396             =head1 NAME
397              
398             HTML::FormFu::Role::Element::Group - Role for grouped form fields
399              
400             =head1 VERSION
401              
402             version 2.07
403              
404             =head1 DESCRIPTION
405              
406             Base class for L<HTML::FormFu::Element::Checkboxgroup>,
407             L<HTML::FormFu::Element::Radiogroup>, and
408             L<HTML::FormFu::Element::Select> fields.
409              
410             =head1 METHODS
411              
412             =head2 options
413              
414             Arguments: none
415              
416             Arguments: \@options
417              
418             ---
419             elements:
420             - type: Select
421             name: foo
422             options:
423             - [ 01, January ]
424             - [ 02, February ]
425             - value: 03
426             label: March
427             attributes:
428             style: highlighted
429             - [ 04, April ]
430              
431             If passed no arguments, it returns an arrayref of the currently set options.
432              
433             Use to set the list of items in the select menu / radiogroup.
434              
435             Its arguments must be an array-ref of items. Each item may be an array ref
436             of the form C<[ $value, $label ]> or a hash-ref of the form
437             C<< { value => $value, label => $label } >>. Each hash-ref may also have an
438             C<attributes> key.
439              
440             Passing an item containing a C<group> key will, for
441             L<Select fields|HTML::FormFu::Element::Select>, create an optgroup. And for
442             L<Radiogroup fields|HTML::FormFu::Element::Radiogroup> or
443             L<Checkboxgroup fields|HTML::FormFu::Element::Checkboxgroup>, create a
444             sub-group of radiobuttons or checkboxes with a new C<span> block, with the
445             classname C<subgroup>.
446              
447             An example of Select optgroups:
448              
449             ---
450             elements:
451             - type: Select
452             name: foo
453             options:
454             - label: "group 1"
455             group:
456             - [1a, 'item 1a']
457             - [1b, 'item 1b']
458             - label: "group 2"
459             group:
460             - [2a, 'item 2a']
461             - [2b, 'item 2b']
462              
463             When using the hash-ref construct, the C<label_xml> and C<label_loc>
464             variants of C<label> are supported, as are the C<value_xml> and C<value_loc>
465             variants of C<value>, the C<attributes_xml> variant of C<attributes> and the
466             C<label_attributes_xml> variant of C<label_attributes>.
467              
468             C<container_attributes> or C<container_attributes_xml> is used by
469             L<HTML::FormFu::Element::Checkboxgroup> and
470             L<HTML::FormFu::Element::Radiogroup> for the c<span> surrounding each
471             item's input and label. It is ignored by L<HTML::FormFu::Element::Select>
472             elements.
473              
474             C<label_attributes> / C<label_attributes_xml> is used by
475             L<HTML::FormFu::Element::Checkboxgroup> and
476             L<HTML::FormFu::Element::Radiogroup> for the c<label> tag of each item.
477             It is ignored by L<HTML::FormFu::Element::Select> elements.
478              
479             =head2 values
480              
481             Arguments: \@values
482              
483             ---
484             elements:
485             - type: Radiogroup
486             name: foo
487             values:
488             - jan
489             - feb
490             - mar
491             - apr
492              
493             A more concise alternative to L</options>. Use to set the list of values in
494             the select menu / radiogroup.
495              
496             Its arguments must be an array-ref of values. The labels used are the
497             result of C<ucfirst($value)>.
498              
499             =head2 value_range
500              
501             Arguments: \@values
502              
503             ---
504             elements:
505             - type: Select
506             name: foo
507             value_range:
508             - ""
509             - 1
510             - 12
511              
512             Similar to L</values>, but the last 2 values are expanded to a range. Any
513             preceding values are used literally, allowing the common empty first item
514             in select menus.
515              
516             =head2 empty_first
517              
518             If true, then a blank option will be inserted at the start of the option list
519             (regardless of whether L</options>, L</values> or L</value_range> was used to
520             populate the options). See also L</empty_first_label>.
521              
522             =head2 empty_first_label
523              
524             =head2 empty_first_label_xml
525              
526             =head2 empty_first_label_loc
527              
528             If L</empty_first> is true, and L</empty_first_label> is set, this value will
529             be used as the label for the first option - so only the first option's value
530             will be empty.
531              
532             =head1 SEE ALSO
533              
534             Is a sub-class of, and inherits methods from
535             L<HTML::FormFu::Role::Element::Field>, L<HTML::FormFu::Element>
536              
537             L<HTML::FormFu>
538              
539             =head1 AUTHOR
540              
541             Carl Franks, C<cfranks@cpan.org>
542              
543             =head1 LICENSE
544              
545             This library is free software, you can redistribute it and/or modify it under
546             the same terms as Perl itself.
547              
548             =head1 AUTHOR
549              
550             Carl Franks <cpan@fireartist.com>
551              
552             =head1 COPYRIGHT AND LICENSE
553              
554             This software is copyright (c) 2018 by Carl Franks.
555              
556             This is free software; you can redistribute it and/or modify it under
557             the same terms as the Perl 5 programming language system itself.
558              
559             =cut