File Coverage

blib/lib/Rose/HTML/Form/Field/Group.pm
Criterion Covered Total %
statement 256 304 84.2
branch 84 122 68.8
condition 30 47 63.8
subroutine 40 55 72.7
pod 9 39 23.0
total 419 567 73.9


line stmt bran cond sub pod time code
1             package Rose::HTML::Form::Field::Group;
2              
3 13     13   104 use strict;
  13         27  
  13         345  
4              
5 13     13   60 use Carp();
  13         26  
  13         201  
6              
7             # XXX: Can't use Scalar::Defer 0.11 (or possibly later) until some things
8             # XXX: are sorted out. See: http://rt.cpan.org/Ticket/Display.html?id=31039
9             # XXX: Scalar::Defer 0.18 seems to work again. Yay!
10             # XXX: ...but this whole approach is a bt too clever.
11             #use Scalar::Defer();
12              
13 13     13   67 use Scalar::Util();
  13         22  
  13         183  
14 13     13   93 use Rose::HTML::Util();
  13         25  
  13         324  
15              
16 13     13   69 use base 'Rose::HTML::Form::Field';
  13         48  
  13         2787  
17              
18             our $VERSION = '0.606';
19              
20             our $Debug = undef;
21              
22             use Rose::Object::MakeMethods::Generic
23             (
24 13         150 scalar => [ qw(rows columns) ],
25              
26             'scalar --get_set_init' =>
27             [
28             qw(html_linebreak xhtml_linebreak)
29             ],
30              
31             boolean => [ 'linebreak' => { default => 1 } ],
32 13     13   106 );
  13         29  
33              
34             sub init
35             {
36 112     112 1 282 my($self) = shift;
37              
38 112         488 $self->{'items'} = [];
39 112         313 $self->{'values'} = {};
40 112         315 $self->{'labels'} = {};
41 112         424 $self->{'defaults'} = {};
42              
43 112         464 $self->SUPER::init(@_);
44             }
45              
46 13     13   2053 use constant HTML_LINEBREAK => "<br>\n";
  13         36  
  13         967  
47 13     13   88 use constant XHTML_LINEBREAK => "<br />\n";
  13         25  
  13         49069  
48              
49 8     8 0 208 sub init_html_linebreak { HTML_LINEBREAK }
50 0     0 0 0 sub init_xhtml_linebreak { XHTML_LINEBREAK }
51              
52 0     0   0 sub _item_class { '' }
53 153     153   353 sub _item_group_class { '' }
54 0     0   0 sub _item_name { 'item' }
55 0     0   0 sub _item_name_plural { 'items' }
56              
57             sub children
58             {
59 8 50   8 1 1275 Carp::croak "Cannot set children() for a pseudo-group ($_[0])" if(@_ > 1);
60 8 100       62 return wantarray ? () : [];
61             }
62              
63 1     1 0 4 sub is_flat_group { 1 }
64              
65             sub items
66             {
67 2719     2719 0 5200 my($self) = shift;
68              
69 2719 100       5937 if(@_)
70             {
71 82         655 $self->{'items'} = $self->_args_to_items({ localized => 0 }, @_);
72 82         525 $self->label_items;
73 82         380 $self->init_items;
74             }
75              
76 2719 50       5782 return (wantarray) ? @{$self->{'items'} || []} : $self->{'items'};
  2393 100       9139  
77             }
78              
79             sub visible_items
80             {
81 219 50   219 0 518 my $items = shift->items or return;
82 219 100       617 return wantarray ? (grep { !$_->hidden } @$items) : [ grep { !$_->hidden } @$items ];
  931         2176  
  35         79  
83             }
84              
85             sub show_all_items
86             {
87 4     4 0 161 my($self) = shift;
88              
89 4         227 foreach my $item ($self->items)
90             {
91 22         206 $item->hidden(0);
92             }
93             }
94              
95             sub hide_all_items
96             {
97 4     4 0 17 my($self) = shift;
98              
99 4         14 foreach my $item ($self->items)
100             {
101 20         51 $item->hidden(1);
102             }
103             }
104              
105             sub delete_item
106             {
107 18     18 0 50 my($self, $value) = @_;
108              
109 18 100       69 my $delete_item = $self->item($value) or return;
110 14         60 my $group_class = $self->_item_group_class;
111              
112 14   50     53 my $items = $self->items || [];
113              
114 14         29 my $i = 0;
115              
116 14         38 foreach my $item (@$items)
117             {
118 47 100       145 if($item->isa($group_class))
119             {
120 2 50       50 if(my $deleted = $item->delete_item($value))
121             {
122 2         18 return $deleted;
123             }
124             }
125              
126 45 100       137 last if($item eq $delete_item);
127 33         57 $i++;
128             }
129              
130 12         128 return splice(@$items, $i, 1);
131             }
132              
133 0     0 0 0 sub push_items { shift->add_items(@_) }
134 0     0 0 0 sub push_item { shift->add_item(@_) }
135              
136             sub pop_items
137             {
138 0     0 0 0 my($self) = shift;
139              
140 0   0     0 my $items = $self->items || [];
141              
142 0 0       0 if(@_)
143             {
144 0         0 my $offset = $#$items - $_[0];
145 0 0       0 return splice(@$items, $offset < 0 ? 0 : $offset)
146             }
147              
148 0         0 my @items = pop(@$items);
149 0 0       0 $self->init_items if(@items);
150 0         0 return @items;
151             }
152              
153 0     0 0 0 sub pop_item { shift->pop_items(@_) }
154              
155             sub shift_items
156             {
157 0     0 0 0 my($self) = shift;
158              
159 0   0     0 my $items = $self->items || [];
160              
161 0 0       0 my @items = @_ ? splice(@$items, 0, $_[0]) : shift(@$items);
162 0 0       0 $self->init_items if(@items);
163 0         0 return @items;
164             }
165              
166 0     0 0 0 sub shift_item { shift->shift_items(@_) }
167              
168             sub unshift_items
169             {
170 0     0 0 0 my($self) = shift;
171              
172 0         0 unshift(@{$self->{'items'}}, $self->_args_to_items({ localized => 0 }, @_));
  0         0  
173              
174 0         0 $self->init_items;
175             }
176              
177 0     0 0 0 sub unshift_item { shift->unshift_items(@_) }
178              
179             sub delete_items
180             {
181 4     4 0 17 my($self) = shift;
182              
183 4         16 foreach my $arg (@_)
184             {
185 12         54 $self->delete_item($arg);
186             }
187             }
188              
189             sub delete_item_group
190             {
191 2     2 0 11 my($self, $value) = @_;
192              
193 2         9 my $group_class = $self->_item_group_class;
194 2 50 50     69 my $delete_item = UNIVERSAL::isa($value, $group_class) ? $value : ($self->item_group($value) or return);
195              
196 2   50     10 my $items = $self->items || [];
197              
198 2         5 my $i = 0;
199              
200 2         9 foreach my $item (@$items)
201             {
202 4 100       15 last if($item eq $delete_item);
203              
204 2 50       12 if($item->isa($group_class))
205             {
206 0 0       0 if(my $deleted = $item->delete_item($value))
207             {
208 0         0 return $deleted;
209             }
210             }
211              
212 2         5 $i++;
213             }
214              
215 2         12 return splice(@$items, $i, 1);
216             }
217              
218             sub delete_item_groups
219             {
220 0     0 0 0 my($self) = shift;
221              
222 0         0 foreach my $arg (@_)
223             {
224 0         0 $self->delete_item_group($arg);
225             }
226             }
227              
228             sub items_localized
229             {
230 0     0 0 0 my($self) = shift;
231              
232 0 0       0 if(@_)
233             {
234 0         0 $self->{'items'} = $self->_args_to_items({ localized => 1 }, @_);
235 0         0 $self->init_items;
236             }
237              
238 0 0       0 return (wantarray) ? @{$self->{'items'}} : $self->{'items'};
  0         0  
239             }
240              
241             sub items_html_attr
242             {
243 16     16 0 3071 my($self, $name) = (shift, shift);
244              
245 16 100       56 if(@_)
246             {
247 8         30 foreach my $item ($self->items)
248             {
249 28         78 $item->html_attr($name, @_);
250             }
251              
252 8         23 return @_;
253             }
254              
255 8         21 foreach my $item (@{[ $self->items ]})
  8         28  
256             {
257 8         40 return $item->html_attr($name);
258             }
259              
260 0         0 return undef;
261             }
262              
263             sub delete_items_html_attr
264             {
265 8     8 0 21 my($self) = shift;
266              
267 8         35 foreach my $item ($self->items)
268             {
269 28         109 $item->delete_html_attr(@_);
270             }
271             }
272              
273             *fields = \&items;
274             *fields_localized = \&items_localized;
275              
276 42     42   118 sub _html_item { $_[1]->html_field }
277 14     14   41 sub _xhtml_item { $_[1]->xhtml_field }
278              
279             sub _args_to_items
280             {
281 98     98   287 my($self, $options) = (shift, shift);
282              
283 98         216 my(%labels, @choices, $items);
284              
285 98         503 my $class = $self->_item_class;
286 98         498 my $group_class = $self->_item_group_class;
287 98 50       386 my $label_method = $options->{'localized'} ? 'label_id' : 'label';
288              
289 98 100 100     578 if(@_ == 1 && ref $_[0] eq 'HASH')
290             {
291 17         43 %labels = %{$_[0]};
  17         135  
292 17         128 @choices = sort keys %labels;
293             }
294             else
295             {
296 81         178 my $args;
297              
298             # XXX: Hack to allow a reference to an array of plain scalars
299             # XXX: to be taken as a list of values.
300 81 100 100     432 if(@_ == 1 && ref $_[0] eq 'ARRAY')
301             {
302 52         119 $args = $_[0];
303              
304 52 100       151 unless(grep { ref $_ } @$args)
  174         486  
305             {
306 44         119 $args = [ map { $_ => $_ } @$args ];
  130         436  
307             }
308             }
309 29         73 else { $args = \@_ }
310              
311 81         296 while(@$args)
312             {
313 214         428 my $arg = shift(@$args);
314              
315 214 100 100     1769 if(UNIVERSAL::isa($arg, $class) || UNIVERSAL::isa($arg, $group_class))
    50          
316             {
317 17         56 push(@$items, $arg);
318             }
319             elsif(!ref $arg)
320             {
321 197         897 my $item = $class->new(value => $arg);
322              
323 197 100       711 if(!ref $args->[0])
    100          
    50          
324             {
325 181         1220 $item->$label_method(shift(@$args));
326 181         1520 push(@$items, $item);
327             }
328             elsif(ref $args->[0] eq 'HASH')
329             {
330 13         44 my $pairs = shift(@$args);
331              
332 13         76 while(my($method, $value) = each(%$pairs))
333             {
334 13         73 $item->$method($value);
335             }
336              
337 13         112 push(@$items, $item);
338             }
339             elsif(ref $args->[0] eq 'ARRAY')
340             {
341 3         24 my $group = $group_class->new(label => $arg,
342             items => shift @$args);
343 3         41 push(@$items, $group);
344             }
345             else
346             {
347 0         0 Carp::croak "Illegal or incorrectly positioned ", $self->_item_name_plural,
348             " argument: $args->[0]";
349             }
350              
351             }
352             else
353             {
354 0         0 Carp::croak "Illegal or incorrectly positioned ", $self->_item_name_plural,
355             " argument: $args->[0]";
356             }
357             }
358             }
359              
360 98 100       424 if(keys %labels)
361             {
362 17         34 my @items;
363              
364 17         66 my $class = $self->_item_class;
365              
366 17         85 foreach my $value (@choices)
367             {
368             push(@$items, $class->new(value => $value,
369 35         204 $label_method => $labels{$value}));
370             }
371             }
372              
373 98         334 foreach my $item (@$items)
374             {
375             # Connect item to group
376 249 50       793 $item->parent_group($self) if($item->can('parent_group'));
377              
378             # XXX: This whole approach is a bit too clever and leak-prone.
379              
380             # # Speculatively hook up localizer and locale
381             # # XXX: Scalar::Defer 0.11 breaks this (http://rt.cpan.org/Ticket/Display.html?id=31039)
382             # # XXX: Scalar::Defer 0.18 seems to work again. Yay!
383             # Scalar::Util::weaken(my $welf = $self);
384             # $item->localizer(Scalar::Defer::defer { $welf->localizer });
385             # #$item->localizer(sub { $welf->localizer });
386             #
387             # # XXX: Use lame workaround instead.
388             # #Scalar::Util::weaken(my $welf = $self);
389             # #$item->localizer(sub { $welf->localizer });
390             #
391             # if(my $parent = $self->parent_form)
392             # {
393             # # XXX: Scalar::Defer 0.11 breaks this (http://rt.cpan.org/Ticket/Display.html?id=31039)
394             # # XXX: Scalar::Defer 0.18 seems to work again. Yay!
395             # Scalar::Util::weaken(my $warent = $parent);
396             # $item->locale(Scalar::Defer::defer { $warent->locale });
397             # # XXX: Use lame workaround instead.
398             # #$item->locale(sub { $parent->locale });
399             # }
400             }
401              
402 98 100       617 return (wantarray) ? @$items : $items;
403             }
404              
405             sub parent_field
406             {
407 6420     6420 1 10137 my($self) = shift;
408              
409 6420 100       12283 if(@_)
410             {
411 21 50       114 if(my $parent = $self->SUPER::parent_field(@_))
412             {
413 21         88 foreach my $item ($self->items)
414             {
415 63 50       238 $item->parent_field($parent) unless($item->parent_field);
416             }
417             }
418             }
419              
420 6420         14202 return $self->SUPER::parent_field;
421             }
422              
423             # XXX: This whole approach is a bit too clever and leak-prone.
424             # sub parent_form
425             # {
426             # my($self) = shift;
427             #
428             # if(@_)
429             # {
430             # if(my $parent = $self->SUPER::parent_form(@_))
431             # {
432             # foreach my $item ($self->items)
433             # {
434             # # XXX: Scalar::Defer 0.11 breaks this (http://rt.cpan.org/Ticket/Display.html?id=31039)
435             # # XXX: Scalar::Defer 0.18 seems to work again. Yay!
436             # Scalar::Util::weaken(my $warent = $parent);
437             # $item->locale(Scalar::Defer::defer { $warent->locale });
438             # # XXX: Use lame workaround instead.
439             # #$item->locale(sub { $parent->locale });
440             # }
441             # }
442             # }
443             #
444             # return $self->SUPER::parent_form;
445             # }
446              
447             sub add_items
448             {
449 16     16 0 1586 my($self) = shift;
450              
451 16         30 push(@{$self->{'items'}}, $self->_args_to_items({ localized => 0 }, @_));
  16         92  
452              
453 16         88 $self->init_items;
454             }
455              
456             *add_item = \&add_items;
457              
458             sub add_items_localized
459             {
460 0     0 0 0 my($self) = shift;
461              
462 0         0 push(@{$self->{'items'}}, $self->_args_to_items({ localized => 1 }, @_));
  0         0  
463              
464 0         0 $self->init_items;
465             }
466              
467             *add_item_localized = \&add_items_localized;
468              
469             sub label_items
470             {
471 91     91 0 245 my($self) = shift;
472              
473 91   50     311 my $labels = $self->{'labels'} || {};
474 91   100     444 my $label_ids = $self->{'label_ids'} || {};
475              
476 91 100 100     499 return unless(%$labels || %$label_ids);
477              
478 12         258 foreach my $item ($self->items)
479             {
480 34         326 my $value = $item->html_attr('value');
481              
482 34 50       95 next unless(defined $value);
483              
484 34 100       96 if(exists $label_ids->{$value})
    100          
485             {
486 10         43 $item->label_id($label_ids->{$value});
487             }
488             elsif(exists $labels->{$value})
489             {
490 15         52 $item->label($labels->{$value});
491             }
492             }
493             }
494              
495             sub clear
496             {
497 393     393 1 705 my($self) = shift;
498              
499 393         903 $self->{'values'} = undef;
500              
501 393         910 foreach my $item ($self->items)
502             {
503 1240         5876 local $item->{'auto_invalidate_parent'} = $self->auto_invalidate_parent;
504 1240         3029 $item->clear;
505             }
506              
507 393         2611 $self->error(undef);
508 393         1225 $self->has_partial_value(0);
509 393         1903 $self->is_cleared(1);
510              
511 393         1852 $self->init_items;
512             }
513              
514             sub clear_labels
515             {
516 1     1 0 3 my($self) = shift;
517              
518 1         4 delete $self->{'labels'};
519 1         2 delete $self->{'label_ids'};
520              
521 1         4 foreach my $item ($self->items)
522             {
523 7         55 $item->label_id(undef);
524 7         50 $item->label('');
525             }
526              
527 1         9 return;
528             }
529              
530             sub reset_labels
531             {
532 1     1 0 3 my($self) = shift;
533              
534 1         2 delete $self->{'labels'};
535 1         4 delete $self->{'label_ids'};
536              
537 1         3 foreach my $item ($self->items)
538             {
539 7         49 $item->label_id(undef);
540 7         85 $item->label($item->value);
541             }
542              
543 1         9 return;
544             }
545              
546             sub reset
547             {
548 20     20 1 58 my($self) = shift;
549              
550 20         89 $self->input_value(undef);
551              
552 20         95 foreach my $item ($self->items)
553             {
554 88         263 $item->reset;
555             }
556              
557 20         106 $self->error(undef);
558 20         76 $self->has_partial_value(0);
559 20         114 $self->is_cleared(0);
560              
561 20         103 $self->init_items;
562             }
563              
564 9     9 0 159 sub labels { shift->_labels(0, @_) }
565 4     4 0 58 sub label_ids { shift->_labels(1, @_) }
566              
567             sub _labels
568             {
569 13     13   62 my($self, $localized) = (shift, shift);
570              
571 13 100       55 my $key = $localized ? 'label_ids' : 'labels';
572              
573 13 100       45 if(@_)
574             {
575 9         18 my %labels;
576              
577 9 100 66     65 if(@_ == 1 && ref $_[0] eq 'HASH')
578             {
579 8         33 $self->{$key} = $_[0];
580             }
581             else
582             {
583 1 50       4 Carp::croak "Odd number of items found in $key() hash argument"
584             unless(@_ % 2 == 0);
585              
586 1         5 $self->{$key} = { @_ };
587             }
588              
589 9         39 $self->label_items;
590             }
591              
592 13         97 my $want = wantarray;
593              
594 13 100       57 return unless(defined $want);
595              
596 4         16 my $group_class = $self->_item_group_class;
597              
598 4         13 my %labels;
599              
600             # Dumb linear search for now
601 4         16 foreach my $item ($self->items)
602             {
603 12 50       67 if($item->isa($group_class))
604             {
605 0         0 foreach my $subitem ($item->items)
606             {
607 0         0 $labels{$subitem->html_attr('value')} = $subitem->label;
608             }
609             }
610             else
611             {
612 12         37 $labels{$item->html_attr('value')} = $item->label;
613             }
614             }
615              
616 4 50       65 return $want ? %labels : \%labels;
617             }
618              
619             # sub labels
620             # {
621             # my($self) = shift;
622             #
623             # if(@_)
624             # {
625             # my %labels;
626             #
627             # if(@_ == 1 && ref $_[0] eq 'HASH')
628             # {
629             # $self->{'labels'} = $_[0];
630             # }
631             # else
632             # {
633             # Carp::croak "Odd number of items found in labels() hash argument"
634             # unless(@_ % 2 == 0);
635             #
636             # $self->{'labels'} = { @_ };
637             # }
638             #
639             # $self->label_items;
640             # }
641             #
642             # my $want = wantarray;
643             #
644             # return unless(defined $want);
645             #
646             # my $group_class = $self->_item_group_class;
647             #
648             # my %labels;
649             #
650             # # Dumb linear search for now
651             # foreach my $item ($self->items)
652             # {
653             # if($item->isa($group_class))
654             # {
655             # foreach my $subitem ($item->items)
656             # {
657             # $labels{$subitem->html_attr('value')} = $subitem->label;
658             # }
659             # }
660             # else
661             # {
662             # $labels{$item->html_attr('value')} = $item->label;
663             # }
664             # }
665             #
666             # return $want ? %labels : \%labels;
667             # }
668              
669             sub html_field
670             {
671 41     41 1 103 my($self) = shift;
672 41 100       128 my $sep = ($self->linebreak) ? $self->html_linebreak : ' ';
673 41         422 return join($sep, map { $_->html_field } $self->visible_items);
  187         1533  
674             }
675              
676             *html_fields = \&html_field;
677              
678             sub xhtml_field
679             {
680 25     25 1 58 my($self) = shift;
681 25 50       69 my $sep = ($self->linebreak) ? $self->xhtml_linebreak : ' ';
682 25         365 return join($sep, map { $_->xhtml_field } $self->visible_items);
  115         1032  
683             }
684              
685             *xhtml_fields = \&xhtml_field;
686              
687             sub escape_html
688             {
689 16     16 1 41 my($self) = shift;
690              
691 16 100       68 if(@_)
692             {
693 10         48 my $val = $self->SUPER::escape_html(@_);
694              
695 10         77 foreach my $item ($self->items)
696             {
697 30         129 $item->escape_html($val);
698             }
699              
700 10         57 return $val;
701             }
702              
703 6         27 return $self->SUPER::escape_html(@_);
704             }
705              
706             sub hidden_fields
707             {
708 12     12 1 33 my($self) = shift;
709              
710 12         23 my @hidden;
711              
712 12         39 foreach my $item ($self->items)
713             {
714 66 100       173 push(@hidden, $item->hidden_field) if(defined $item->internal_value);
715             }
716              
717 12 50       78 return (wantarray) ? @hidden : \@hidden;
718             }
719              
720             # XXX: Could someday use Rose::HTML::Table::*
721              
722             sub html_table
723             {
724 8     8 0 37 my($self, %args) = @_;
725              
726 8         20 my $items = $args{'items'};
727              
728 8 50 33     36 return unless(ref $items && @$items);
729              
730 8   100     31 my $xhtml = delete $args{'_xhtml'} || 0;
731 8   33     25 my $format_item = $args{'format_item'} || ($xhtml ? \&_xhtml_item : \&_html_item);
732              
733 8         15 my $total = @$items;
734 8   50     72 my $rows = $args{'rows'} || $self->rows || 1;
735 8   100     54 my $cols = $args{'columns'} || $self->columns || 1;
736              
737 8         27 my $per_cell = $total / ($rows * $cols);
738              
739 8 100       23 if($total % ($rows * $cols))
740             {
741 2         21 $per_cell = int($per_cell + 1);
742             }
743              
744 8         14 my @table;
745              
746 8         15 my $i = 0;
747              
748 8         23 for(my $x = 0; $x < $cols; $x++)
749             {
750 10         36 for(my $y = 0; $y < $rows; $y++)
751             {
752 10         21 my $end = $i + $per_cell - 1;
753 10 100       26 $end = $#$items if($end > $#$items);
754 10         49 $table[$y][$x] = [ @$items[$i .. $end] ];
755 10         41 $i += $per_cell;
756             }
757             }
758              
759 8 100       35 my $sep = ($self->linebreak) ? $xhtml ? $self->xhtml_linebreak : $self->html_linebreak : ' ';
    50          
760              
761 8         133 my $html = '<table' . Rose::HTML::Util::html_attrs_string($args{'table'}) . ">\n";
762              
763 8 50       395 my @tr_attrs = (ref $args{'tr'} eq 'ARRAY') ? @{$args{'tr'}} : ($args{'tr'});
  0         0  
764 8 50       25 my @td_attrs = (ref $args{'td'} eq 'ARRAY') ? @{$args{'td'}} : ($args{'td'});
  0         0  
765              
766 8         15 my $tr = 0;
767              
768 8         21 foreach my $col (@table)
769             {
770 8   33     21 my $tr_attrs = $tr_attrs[$tr] || $tr_attrs[-1];
771              
772 8         23 $html .= '<tr' . Rose::HTML::Util::html_attrs_string($tr_attrs) . ">\n";
773              
774 8         16 my $td = 0;
775              
776 8         19 foreach my $row (@$col)
777             {
778 10   66     31 my $td_attrs = $td_attrs[$td] || $td_attrs[-1];
779              
780             $html .= '<td' . Rose::HTML::Util::html_attrs_string($td_attrs) . '>' .
781 10         24 join($sep, map { $self->$format_item($_) } @$row) .
  56         486  
782             "</td>\n";
783              
784 10         161 $td++;
785             }
786              
787 8         27 $html .= "</tr>\n";
788 8         17 $tr++;
789             }
790              
791 8         15 $html .= "</table>\n";
792              
793 8         129 return $html;
794             }
795              
796             *xhtml_table = \&html_table;
797              
798             1;