File Coverage

blib/lib/CGI/Test/Form/Widget/Menu.pm
Criterion Covered Total %
statement 86 110 78.1
branch 17 28 60.7
condition 3 9 33.3
subroutine 19 24 79.1
pod 15 16 93.7
total 140 187 74.8


line stmt bran cond sub pod time code
1             package CGI::Test::Form::Widget::Menu;
2 14     14   620 use strict;
  14         20  
  14         343  
3 14     14   46 use warnings;
  14         11  
  14         239  
4             ##################################################################
5             # $Id: Menu.pm 411 2011-09-26 11:19:30Z nohuhu@nohuhu.org $
6             # $Name: cgi-test_0-104_t1 $
7             ##################################################################
8             #
9             # Copyright (c) 2001, Raphael Manfredi
10             #
11             # You may redistribute only under the terms of the Artistic License,
12             # as specified in the README file that comes with the distribution.
13             #
14              
15 14     14   37 use Carp;
  14         12  
  14         540  
16              
17             #
18             # This class models a FORM menu (either a popup or a scrollable list).
19             #
20              
21 14     14   38 use base qw(CGI::Test::Form::Widget);
  14         19  
  14         788  
22              
23 14     14   7304 use Storable qw(dclone);
  14         31763  
  14         9845  
24              
25             ############################################################
26             #
27             # ->_parse_options
28             #
29             # Parse
30             # We ignore items, since those are only there for grouping options,
31             # and cannot be individually selected as such.
32             #
33             # The following attributes are used to record the options:
34             #
35             # option_labels listref of option labels, in the order they appear
36             # option_values listref of option values, in the order they appear
37             # known_values hashref, recording valid *values*
38             # selected hashref, recording selected *values*
39             # selected_count amount of selected items
40             #
41             ############################################################
42             sub _parse_options
43             {
44 32     32   48 my $this = shift;
45 32         40 my ($node) = shift;
46              
47 32         62 my $labels = $this->{option_labels} = [];
48 32         59 my $values = $this->{option_values} = [];
49 32         64 my $selected = $this->{selected} = {};
50 32         49 my $known = $this->{known_values} = {};
51 32         35 my $count = 0;
52 32         40 my %seen;
53              
54 32     304   143 my @nodes = $node->look_down(sub {1});
  304         2232  
55 32         286 shift @nodes; # first node is the
56              
57 32         54 foreach my $opt (@nodes)
58             {
59 272 50       439 next if $opt->tag() eq "optgroup";
60 272 50       1171 unless ($opt->tag() eq "option")
61             {
62 0         0 warn "ignoring non-option tag '%s' within SELECT",
63             uc($opt->tag());
64 0         0 next;
65             }
66              
67             #
68             # The option label is normally the content of the
69             # However, if there is a LABEL= within the tag, it should be used
70             # in preference to the option content, says the W3C's norm.
71             #
72              
73 272   33     1043 my $label = $opt->attr("label") || $opt->as_text();
74 272         5139 my $is_selected = $opt->attr("selected");
75 272         1481 my $value = $opt->attr("value");
76              
77 272 50       1423 unless (defined $value)
78             {
79 0         0 warn "ignoring OPTION tag with no value: %s", $opt->starttag();
80 0         0 next;
81             }
82              
83             #
84             # It is not really an error to have duplicate values, but is it
85             # a good interface style? The user will be faced with multiple
86             # labels to choose from, some of them being handled in the same way
87             # since they bear the same value... Tough choice... Let's warn!
88             #
89              
90 272 50       571 warn "duplicate value '%s' in OPTION for SELECT NAME=\"%s\"",
91             $value, $this->name
92             if $seen{$value}++;
93              
94 272         293 push @$labels, $label;
95 272         254 push @$values, $value;
96 272         243 $known->{$value}++; # help them spot dups
97 272 100       413 if ($is_selected)
98             {
99 34         49 $selected->{$value}++;
100 34         53 $count++;
101             }
102             }
103              
104             #
105             # A popup menu needs to have at least one item selected. We're the
106             # user agent, and we get to choose which item we'll select implicitely.
107             # Use the first listed value, if any.
108             #
109              
110 32 0 33     86 if ($count == 0 && $this->is_popup() && @$values)
      33        
111             {
112 0         0 my $first = $values->[ 0 ];
113 0         0 $selected->{$first}++;
114 0         0 $count++;
115 0         0 warn "implicitely selecting OPTION '%s' for SELECT NAME=\"%s\"",
116             $first, $this->name();
117             }
118              
119 32         67 $this->{selected_count} = $count;
120              
121 32         109 return;
122             }
123              
124             ############################################################
125             #
126             # ->_is_successful -- defined
127             #
128             # Is the enabled widget "successful", according to W3C's specs?
129             # Any menu with at least one selected item is.
130             #
131             ############################################################
132             sub _is_successful
133             {
134 38     38   44 my $this = shift;
135 38         120 return $this->selected_count > 0;
136             }
137              
138             ############################################################
139             #
140             # ->submit_tuples -- redefined
141             #
142             # Returns list of (name => value) tuples that should be part of the
143             # submitted form data.
144             #
145             ############################################################
146             sub submit_tuples
147             {
148 19     19 1 29 my $this = shift;
149              
150 19         65 my $name = $this->name();
151 19         74 my $selected = $this->selected();
152              
153 19         64 my @tuples =
154 19         36 map {$name => $_} grep {$selected->{$_}} @{$this->option_values()};
  95         128  
  19         111  
155              
156 19         51 return @tuples;
157             }
158              
159             #
160             # Attribute access
161             #
162             ############################################################
163             sub multiple
164             {
165 28     28 1 36 my $this = shift;
166 28         74 return $this->{multiple};
167             } # Set by Menu::List
168              
169             ############################################################
170             sub option_labels
171             {
172 0     0 1 0 my $this = shift;
173 0         0 return $this->{option_labels};
174             }
175             ############################################################
176             sub option_values
177             {
178 21     21 1 29 my $this = shift;
179 21         64 return $this->{option_values};
180             }
181             ############################################################
182             sub known_values
183             {
184 66     66 1 62 my $this = shift;
185 66         199 return $this->{known_values};
186             }
187             ############################################################
188             sub selected
189             {
190 104     104 1 108 my $this = shift;
191 104         248 return $this->{selected};
192             }
193             ############################################################
194             sub selected_count
195             {
196 40     40 1 38 my $this = shift;
197 40         125 return $this->{selected_count};
198             }
199             ############################################################
200             sub old_selected
201             {
202 0     0 0 0 my $this = shift;
203 0         0 return $this->{old_selected};
204             }
205              
206             #
207             # Selection shortcuts
208             #
209              
210             ############################################################
211             sub select
212             {
213 22     22 1 194 my $this = shift;
214 22         36 my $item = shift;
215 22         132 $this->set_selected($item, 1);
216             }
217             ############################################################
218             sub unselect
219             {
220 6     6 1 44 my $this = shift;
221 6         14 my $item = shift;
222 6         14 $this->set_selected($item, 0);
223             }
224              
225             #
226             # Global widget predicates
227             #
228              
229             ############################################################
230             sub is_read_only
231             {
232 0     0 1 0 return 1;
233             }
234              
235             #
236             # High-level classification predicates
237             #
238              
239             ############################################################
240             sub is_menu
241             {
242 2     2 1 8 return 1;
243             }
244              
245             #
246             # Predicates for menus
247             #
248              
249             ############################################################
250             sub is_popup
251             {
252 0     0 1 0 confess "deferred";
253             }
254              
255             ############################################################
256             #
257             # ->is_selected
258             #
259             # Checks whether given value is selected.
260             #
261             ############################################################
262             sub is_selected
263             {
264 38     38 1 59 my $this = shift;
265 38         48 my ($value) = @_;
266              
267 38 50       68 unless ($this->known_values->{$value})
268             {
269 0         0 carp "unknown value \"%s\" in $this", $value;
270 0         0 return 0;
271             }
272              
273 38         96 return exists $this->selected->{$value};
274             }
275              
276             ############################################################
277             #
278             # ->set_selected
279             #
280             # Change "selected" status for a menu value.
281             #
282             ############################################################
283             sub set_selected
284             {
285 28     28 1 30 my $this = shift;
286 28         38 my ($value, $state) = @_;
287              
288 28 50       112 unless ($this->known_values->{$value})
289             {
290 0         0 carp "unknown value \"%s\" in $this", $value;
291 0         0 return;
292             }
293              
294 28         110 my $is_selected = $this->is_selected($value);
295 28 50       64 return if !$is_selected == !$state; # No change // WTF? -nohuhu
296              
297             #
298             # Save selected status for all the values the first time a change is made.
299             #
300              
301 28 100       788 $this->{old_selected} = dclone $this->{selected}
302             unless exists $this->{old_selected};
303              
304             #
305             # If multiple selection is not authorized, clear the selection list.
306             #
307              
308 28         56 my $selected = $this->selected();
309 28 100       126 %$selected = () unless $this->multiple();
310              
311 28 100       72 $selected->{$value} = 1 if $state;
312 28 100       58 delete $selected->{$value} unless $state;
313 28         44 $this->{selected_count} = scalar keys %$selected;
314              
315 28         60 return;
316             }
317              
318             ############################################################
319             #
320             # ->reset_state
321             #
322             # Called when a "Reset" button is pressed to restore the value the widget
323             # had upon form entry.
324             #
325             ############################################################
326             sub reset_state
327             {
328 0     0 1   my $this = shift;
329              
330 0 0         return unless exists $this->{old_selected};
331 0           $this->{selected} = delete $this->{old_selected};
332 0           $this->{selected_count} = scalar keys %{$this->selected()};
  0            
333              
334 0           return;
335             }
336              
337             1;
338              
339             =head1 NAME
340              
341             CGI::Test::Form::Widget::Menu - Abstract representation of a menu
342              
343             =head1 SYNOPSIS
344              
345             # Inherits from CGI::Test::Form::Widget
346              
347             =head1 DESCRIPTION
348              
349             This class is the abstract representation of a menu from which one can choose
350             one or several items, i.e. either a popup menu or a scrollable list
351             (with possibly multiple selections).
352              
353             There is an interface to query the selected items, get at the presented
354             labels and associated values, and naturally C or C
355             items.
356              
357             =head1 INTERFACE
358              
359             The interface is the same as the one described in L,
360             with the following additions:
361              
362             =head2 Attributes
363              
364             =over 4
365              
366             =item C
367              
368             An hash reference, recording valid menu values, as tuples
369             (I => I), with I set to the number of times the same
370             value is re-used amongst the proposed options.
371              
372             =item C
373              
374             Whether menu allows multiple selections.
375              
376             =item C
377              
378             A list reference, providing the labels to choose from, in the order in which
379             they appear. The retained labels are either the content of the
380             elements, or the value of their C
381              
382             =item C
383              
384             A list reference, providing the underlying values that the user chooses from
385             when he selects labels, in the order in which they appear in the menu.
386              
387             =item C
388              
389             An hash reference, whose keys are the selected values.
390              
391             =item C
392              
393             The amount of currently selected items.
394              
395             =back
396              
397             =head2 Attribute Setting
398              
399             =over 4
400              
401             =item C
402              
403             Mark the option I as selected. If C is false, any
404             previously selected value is automatically unselected.
405              
406             Note that this takes a I, not a I
407              
408             =item C I
409              
410             Unselect an option I. It is not possible to do that on a popup
411             menu: you must C
412              
413             =back
414              
415             =head2 Menu Probing
416              
417             =over 4
418              
419             =item C I
420              
421             Test whether an option I is currently selected or not. This is
422             not testing a label, but a value, which is what the script will get back
423             eventually: labels are there for human consumption only.
424              
425             =back
426              
427             =head2 Widget Classification Predicates
428              
429             There is an additional predicate to distinguish between a popup menu (single
430             selection mandatory) from a scrolling list (multiple selection allowed, and
431             may select nothing).
432              
433             =over 4
434              
435             =item C
436              
437             Returns I for a popup menu.
438              
439             =back
440              
441             =head2 Miscellaneous Features
442              
443             Although documented, those features are more targetted for
444             internal use...
445              
446             =over 4
447              
448             =item C I, I
449              
450             Change the selection status of an option I.
451              
452             You should use the C
453             of calling this feature.
454              
455             =back
456              
457             =head1 AUTHORS
458              
459             The original author is Raphael Manfredi.
460              
461             Steven Hilton was long time maintainer of this module.
462              
463             Current maintainer is Alexander Tokarev Ftokarev@cpan.orgE>.
464              
465             =head1 SEE ALSO
466              
467             CGI::Test::Form::Widget(3),
468             CGI::Test::Form::Widget::Menu::List(3),
469             CGI::Test::Form::Widget::Menu::Popup(3).
470              
471             =cut
472