File Coverage

blib/lib/HTML/FormHandler/Field/Select.pm
Criterion Covered Total %
statement 134 147 91.1
branch 68 88 77.2
condition 32 49 65.3
subroutine 22 24 91.6
pod 2 13 15.3
total 258 321 80.3


line stmt bran cond sub pod time code
1             package HTML::FormHandler::Field::Select;
2             # ABSTRACT: select fields
3             $HTML::FormHandler::Field::Select::VERSION = '0.40068';
4 41     41   39014 use Moose;
  41         114  
  41         343  
5             extends 'HTML::FormHandler::Field';
6 41     41   298697 use Carp;
  41         124  
  41         3043  
7              
8 41     41   13404 use HTML::Entities;
  41         131047  
  41         69668  
9              
10              
11             has 'options' => (
12             isa => 'HFH::SelectOptions',
13             is => 'rw',
14             coerce => 1,
15             traits => ['Array'],
16             auto_deref => 1,
17             handles => {
18             all_options => 'elements',
19             reset_options => 'clear',
20             clear_options => 'clear',
21             has_options => 'count',
22             num_options => 'count',
23             },
24             lazy => 1,
25             builder => 'build_options'
26             );
27 2     2 0 54 sub options_ref { [shift->options] }
28             # this is used for rendering
29             has 'options_index' => ( traits => ['Counter'], isa => 'Num',
30             is => 'rw', default => 0,
31             handles => { inc_options_index => 'inc', dec_options_index => 'dec',
32             reset_options_index => 'reset' },
33             );
34             sub clear_data {
35 61     61 0 166 my $self = shift;
36 61         308 $self->next::method();
37 61         2313 $self->reset_options_index;
38             }
39              
40 59     59 0 1721 sub build_options { [] }
41             has 'options_from' => ( isa => 'Str', is => 'rw', default => 'none' );
42             has 'do_not_reload' => ( isa => 'Bool', is => 'ro' );
43             has 'no_option_validation' => ( isa => 'Bool', is => 'rw' );
44             has 'option_wrapper' => ( is => 'rw' );
45              
46             sub BUILD {
47 101     101 0 282 my $self = shift;
48              
49 101         845 $self->build_options_method;
50 101 100 66     3327 if( $self->options && $self->has_options ) {
51 42         1376 $self->options_from('build');
52 42         1215 $self->default_from_options([$self->options]);
53             }
54 101         3490 $self->input_without_param; # vivify
55             }
56              
57             has 'options_method' => (
58             traits => ['Code'],
59             is => 'ro',
60             isa => 'CodeRef',
61             writer => '_set_options_method',
62             predicate => 'has_options_method',
63             handles => { 'get_options' => 'execute_method' },
64             );
65              
66             sub build_options_method {
67 101     101 0 239 my $self = shift;
68              
69 101         3198 my $set_options = $self->set_options;
70 101   33     723 $set_options ||= "options_" . HTML::FormHandler::Field::convert_full_name($self->full_name);
71 101 100 100     2749 if ( $self->form && $self->form->can($set_options) ) {
72 51         1332 my $attr = $self->form->meta->find_method_by_name( $set_options );
73 51 100 66     11710 if ( $attr and $attr->isa('Moose::Meta::Method::Accessor') ) {
74 2     8   93 $self->_set_options_method( sub { my $self = shift; $self->form->$set_options; } );
  8         20  
  8         204  
75             }
76             else {
77 49     140   3109 $self->_set_options_method( sub { my $self = shift; $self->form->$set_options($self); } );
  140         1230  
  140         3657  
78             }
79             }
80             }
81              
82             has 'sort_options_method' => (
83             traits => ['Code'],
84             is => 'rw',
85             isa => 'CodeRef',
86             predicate => 'has_sort_options_method',
87             handles => {
88             sort_options => 'execute_method',
89             },
90             );
91              
92             has 'set_options' => ( isa => 'Str', is => 'ro');
93              
94             has 'multiple' => ( isa => 'Bool', is => 'rw', default => '0' );
95             # following is for unusual case where a multiple select is a has_many type relation
96             has 'has_many' => ( isa => 'Str', is => 'rw' );
97             has 'size' => ( isa => 'Int|Undef', is => 'rw' );
98             has 'label_column' => ( isa => 'Str', is => 'rw', default => 'name' );
99             has 'localize_labels' => ( isa => 'Bool', is => 'rw' );
100             has 'active_column' => ( isa => 'Str', is => 'rw', default => 'active' );
101             has 'auto_widget_size' => ( isa => 'Int', is => 'rw', default => '0' );
102             has 'sort_column' => ( isa => 'Str|ArrayRef[Str]', is => 'rw' );
103             has '+widget' => ( default => 'Select' );
104 0     0 0 0 sub html_element { 'select' }
105             has '+type_attr' => ( default => 'select' );
106             has 'empty_select' => ( isa => 'Str', is => 'rw' );
107             has '+deflate_method' => ( default => sub { \&select_deflate } );
108             has '+input_without_param' => ( lazy => 1, builder => 'build_input_without_param' );
109             sub build_input_without_param {
110 101     101 0 253 my $self = shift;
111 101 100       3243 if( $self->multiple ) {
112 37         1172 $self->not_nullable(1);
113 37         1117 return [];
114             }
115             else {
116 64         1973 return '';
117             }
118             }
119             has 'value_when_empty' => ( is => 'ro', lazy => 1, builder => 'build_value_when_empty' );
120             sub build_value_when_empty {
121 26     26 0 62 my $self = shift;
122 26 50       736 return [] if $self->multiple;
123 0         0 return undef;
124             }
125              
126             our $class_messages = {
127             'select_not_multiple' => 'This field does not take multiple values',
128             'select_invalid_value' => '\'[_1]\' is not a valid value',
129             };
130              
131             sub get_class_messages {
132 27     27 0 63 my $self = shift;
133             return {
134 27         52 %{ $self->next::method },
  27         160  
135             %$class_messages,
136             }
137             }
138              
139             sub select_widget {
140 0     0 1 0 my $field = shift;
141              
142 0         0 my $size = $field->auto_widget_size;
143 0 0 0     0 return $field->widget unless $field->widget eq 'Select' && $size;
144 0   0     0 my $options = $field->options || [];
145 0 0       0 return 'Select' if @$options > $size;
146 0 0       0 return $field->multiple ? 'checkbox_group' : 'radio_group';
147             }
148              
149             sub as_label {
150 4     4 1 18 my ( $self, $value ) = @_;
151              
152 4 100       29 $value = $self->value unless defined $value;
153 4 50       17 return unless defined $value;
154 4 50       104 if ( $self->multiple ) {
155 4 100       17 unless ( ref($value) eq 'ARRAY' ) {
156 1 50       35 if( $self->has_inflate_default_method ) {
157 1         37 my @values = $self->inflate_default($value);
158 1         3 $value = \@values;
159             }
160             else {
161             # not sure under what circumstances this would happen, but
162             # just in case
163 0         0 return $value;
164             }
165             }
166 4         11 my @labels;
167             my %value_hash;
168 4         19 @value_hash{@$value} = ();
169 4         110 for ( $self->options ) {
170 12 100       41 if ( exists $value_hash{$_->{value}} ) {
171 8         504 push @labels, $_->{label};
172 8         23 delete $value_hash{$_->{value}};
173 8 100       31 last unless keys %value_hash;
174             }
175             }
176 4         15 my $str = join(', ', @labels);
177 4         27 return $str;
178             }
179             else {
180 0         0 for ( $self->options ) {
181 0 0       0 return $_->{label} if $_->{value} eq $value;
182             }
183             }
184 0         0 return;
185             }
186              
187             sub _inner_validate_field {
188 113     113   292 my ($self) = @_;
189              
190 113         504 my $value = $self->value;
191 113 50       355 return unless defined $value; # nothing to check
192              
193 113 100 66     3536 if ( ref $value eq 'ARRAY' &&
    100 100        
      100        
194             !( $self->can('multiple') && $self->multiple ) )
195             {
196 2         12 $self->add_error( $self->get_message('select_not_multiple') );
197 2         7 return;
198             }
199             elsif ( ref $value ne 'ARRAY' && $self->multiple ) {
200 1         3 $value = [$value];
201 1         5 $self->_set_value($value);
202             }
203              
204 111 100       3472 return if $self->no_option_validation;
205              
206             # create a lookup hash
207 110         226 my %options;
208 110         229 foreach my $opt ( @{ $self->options } ) {
  110         2993  
209 1431 100       2700 if ( exists $opt->{group} ) {
210 12         19 foreach my $group_opt ( @{ $opt->{options} } ) {
  12         27  
211 36         105 $options{$group_opt->{value}} = 1;
212             }
213             }
214             else {
215 1419         2957 $options{$opt->{value}} = 1;
216             }
217             }
218 110 100       3239 if( $self->has_many ) {
219 2         9 $value = [map { $_->{$self->has_many} } @$value];
  3         97  
220             }
221 110 100       967 for my $value ( ref $value eq 'ARRAY' ? @$value : ($value) ) {
222 123 100       467 unless ( $options{$value} ) {
223 25         131 my $opt_value = encode_entities($value);
224 25         629 $self->add_error($self->get_message('select_invalid_value'), $opt_value);
225 25         135 return;
226             }
227             }
228 85         342 return 1;
229             }
230              
231             sub _result_from_object {
232 20     20   60 my ( $self, $result, $item ) = @_;
233              
234 20         132 $result = $self->next::method( $result, $item );
235 20         99 $self->_load_options;
236 20 50 33     633 $result->_set_value($self->default)
237             if( defined $self->default && not $result->has_value );
238 20         90 return $result;
239             }
240              
241             sub _result_from_fields {
242 125     125   390 my ( $self, $result ) = @_;
243              
244 125         820 $result = $self->next::method($result);
245 125         709 $self->_load_options;
246 125 100 100     3269 $result->_set_value($self->default)
247             if( defined $self->default && not $result->has_value );
248 125         421 return $result;
249             }
250              
251             sub _result_from_input {
252 79     79   292 my ( $self, $result, $input, $exists ) = @_;
253              
254 79 100       2278 $input = ref $input eq 'ARRAY' ? $input : [$input]
    100          
255             if $self->multiple;
256 79         460 $result = $self->next::method( $result, $input, $exists );
257 79         325 $self->_load_options;
258 79 100 100     2157 $result->_set_value($self->default)
259             if( defined $self->default && not $result->has_value );
260 79         279 return $result;
261             }
262              
263             sub _load_options {
264 224     224   548 my $self = shift;
265              
266             return
267 224 100 66     6755 if ( $self->options_from eq 'build' ||
      66        
268             ( $self->has_options && $self->do_not_reload ) );
269 160         392 my @options;
270 160 100       5433 if( $self->has_options_method ) {
    50          
271 150         5448 @options = $self->get_options;
272 150         5401 $self->options_from('method');
273             }
274             elsif ( $self->form ) {
275 10         20 my $full_accessor;
276 10 50       262 $full_accessor = $self->parent->full_accessor if $self->parent;
277 10         232 @options = $self->form->lookup_options( $self, $full_accessor );
278 10 50       40 $self->options_from('model') if scalar @options;
279             }
280 160 100       551 return unless @options; # so if there isn't an options method and no options
281             # from a table, already set options attributes stays put
282              
283             # possibilities:
284             # @options = ( 1 => 'one', 2 => 'two' );
285             # @options = ([ 1 => 'one', 2 => 'tw' ]);
286             # @options = ([ { value => 1, label => 'one'}, { value => 2, label => 'two'}]);
287             # @options = ([[ 'one', 'two' ]]);
288 150 100       575 my $opts = ref $options[0] eq 'ARRAY' ? $options[0] : \@options;
289 150 100       564 if ( ref $opts->[0] eq 'HASH' ) {
290             # do the array of hashrefs separately so we can get the defaults
291 18         81 $self->default_from_options($opts);
292             }
293 150         4384 $opts = $self->options($opts); # coerce will reformat
294 150 50       509 if ($opts) {
295             # sort options if sort method exists
296 150 100       5703 $opts = $self->sort_options($opts) if $self->has_sort_options_method;
297 150         4139 $self->options($opts);
298             }
299             }
300              
301             # This is because setting 'checked => 1' or 'selected => 1' in an options
302             # hashref is the equivalent of setting a default on the field. Originally
303             # that was handled only in rendering, but it moved knowledge about where
304             # the 'fif' value came from into the renderer, which was bad. So instead
305             # we're setting the defaults from the options.
306             # It's probably better to use 'defaults' to start with, but since there are
307             # people using this method, this at least normalizes it.
308             sub default_from_options {
309 60     60 0 202 my ( $self, $options ) = @_;
310              
311 60 50       201 my @defaults = map { $_->{value} } grep { $_->{checked} || $_->{selected} } @$options;
  2         10  
  408         1476  
312 60 100       252 if( scalar @defaults ) {
313 2 50       80 if( $self->multiple ) {
314 0         0 $self->default(\@defaults);
315             }
316             else {
317 2         70 $self->default($defaults[0]);
318             }
319             }
320             }
321              
322             before 'value' => sub {
323             my $self = shift;
324              
325             return undef unless $self->has_result;
326             my $value = $self->result->value;
327             if( $self->multiple ) {
328             if ( !defined $value || $value eq '' || ( ref $value eq 'ARRAY' && scalar @$value == 0 ) ) {
329             $self->_set_value( $self->value_when_empty );
330             }
331             elsif ( $self->has_many && scalar @$value && ref($value->[0]) ne 'HASH' ) {
332             my @new_values;
333             foreach my $ele (@$value) {
334             push @new_values, { $self->has_many => $ele };
335             }
336             $self->_set_value( \@new_values );
337             }
338             }
339             };
340              
341             sub select_deflate {
342 129     129 0 363 my ( $self, $value ) = @_;
343              
344 129 100 66     3675 return $value unless ( $self->has_many && $self->multiple );
345              
346             # the following is for the edge case of a has_many select
347 5 100 50     56 return $value unless ref($value) eq 'ARRAY' && scalar @$value && ref($value->[0]) eq 'HASH';
      66        
348 3         11 return [map { $_->{$self->has_many} } @$value];
  3         132  
349             }
350              
351             __PACKAGE__->meta->make_immutable;
352 41     41   463 use namespace::autoclean;
  41         117  
  41         551  
353             1;
354              
355             __END__
356              
357             =pod
358              
359             =encoding UTF-8
360              
361             =head1 NAME
362              
363             HTML::FormHandler::Field::Select - select fields
364              
365             =head1 VERSION
366              
367             version 0.40068
368              
369             =head1 DESCRIPTION
370              
371             This is a field that includes a list of possible valid options.
372             This can be used for select and multiple-select fields.
373             Widget type is 'select'.
374              
375             Because select lists and checkbox_groups do not return an HTTP
376             parameter when the entire list is unselected, the Select field
377             must assume that the lack of a param means unselection. So to
378             avoid setting a Select field, it must be set to inactive, not
379             merely not included in the HTML for a form.
380              
381             This field type can also be used for fields that use the
382             'radio_group' widget, and the 'checkbox_group' widget (for
383             selects with multiple flag turned on, or that use the Multiple
384             field).
385              
386             =head2 options
387              
388             The 'options' array can come from a number of different places:
389              
390             =over 4
391              
392             =item From a field declaration
393              
394             In a field declaration:
395              
396             has_field 'opt_in' => ( type => 'Select', widget => 'RadioGroup',
397             options => [{ value => 0, label => 'No'}, { value => 1, label => 'Yes'} ] );
398              
399             =item From a field class 'build_options' method
400              
401             In a custom field class:
402              
403             package MyApp::Field::WeekDay;
404             use Moose;
405             extends 'HTML::FormHandler::Field::Select';
406             ....
407             sub build_options {
408             my $i = 0;
409             my @days = ('Sunday', 'Monday', 'Tuesday', 'Wednesday',
410             'Thursday', 'Friday', 'Saturday' );
411             return [
412             map {
413             { value => $i++, label => $_ }
414             } @days
415             ];
416             }
417              
418             =item From a coderef supplied to the field definition
419              
420             has_field 'flim' => ( type => 'Select', options_method => \&flim_options );
421             sub flim_options { <return options array> }
422              
423             =item From a form 'options_<field_name>' method or attribute
424              
425             has_field 'fruit' => ( type => 'Select' );
426             sub options_fruit {
427             return (
428             1 => 'apples',
429             2 => 'oranges',
430             3 => 'kiwi',
431             );
432             }
433             -- or --
434             has 'options_fruit' => ( is => 'rw', traits => ['Array'],
435             default => sub { [1 => 'apples', 2 => 'oranges',
436             3 => 'kiwi'] } );
437              
438             Notice that, as a convenience, you can return a simple array (or arrayref)
439             for the options array in the 'options_field_name' method. The hashrefs with
440             'value' and 'label' keys will be constructed for you by FormHandler.
441              
442             =item From the database
443              
444             The final source of the options array is a database when the name of the
445             accessor is a relation to the table holding the information used to construct
446             the select list. The primary key is used as the value. The other columns used are:
447              
448             label_column -- Used for the labels in the options (default 'name')
449             active_column -- The name of the column to be used in the query (default 'active')
450             that allows the rows retrieved to be restricted
451             sort_column -- The name or arrayref of names of the column(s) used to sort the options
452              
453             See also L<HTML::FormHandler::Model::DBIC>, the 'lookup_options' method.
454              
455             =back
456              
457             The options field should contain one of the following data structures:
458              
459             =over
460              
461             =item ArrayRef of HashRefs
462              
463             Each hash reference defines an option, with the label and value
464             attributes corresponding to those of the HTML field.
465              
466             =item ArrayRef
467              
468             A list of key/value pairs corresponding to HTML field values and labels.
469              
470             =item ArrayRef containing one ArrayRef
471              
472             Each item inside the inner ArrayRef defines both the label and value of
473             an option.
474              
475             =back
476              
477             =head2 Customizing options
478              
479             Additional attributes can be added in the options array hashref, by using
480             the 'attributes' key. If you have custom rendering code, you can add any
481             additional key that you want, of course.
482              
483             Note that you should *not* set 'checked' or 'selected' attributes in options.
484             That is handled by setting a field default.
485              
486             An options array with an extra 'note' key:
487              
488             sub options_license
489             {
490             my $self = shift;
491             return unless $self->schema;
492             my $licenses = $self->schema->resultset('License')->search({active => 1},
493             {order_by => 'sequence'});
494             my @selections;
495             while ( my $license = $licenses->next ) {
496             push @selections, { value => $license->id, label => $license->label,
497             note => $license->note };
498             }
499             return @selections;
500             }
501              
502             Setting the select element to disabled:
503              
504             sub options_license
505             {
506             my $self = shift;
507             return unless $self->schema;
508             my $licenses = $self->schema->resultset('License')->search(undef,
509             {order_by => 'sequence'});
510             my @selections;
511             while ( my $license = $licenses->next ) {
512             push @selections, { value => $license->id, label => $license->label,
513             attributes => { disabled => ($license->active == 0) ? 1 : 0 } };
514             }
515             return @selections;
516             }
517              
518             You can also divide the options up into option groups. See the section on
519             rendering.
520              
521             =head2 Reloading options
522              
523             If the options come from the options_<fieldname> method or the database, they
524             will be reloaded every time the form is reloaded because the available options
525             may have changed. To prevent this from happening when the available options are
526             known to be static, set the 'do_not_reload' flag, and the options will not be
527             reloaded after the first time
528              
529             =head2 Sorting options
530              
531             The sorting of the options may be changed using a 'sort_options' method in a
532             custom field class. The 'Multiple' field uses this method to put the already
533             selected options at the top of the list. Note that this won't work with
534             option groups.
535              
536             =head1 Attributes and Methods
537              
538             =head2 options
539              
540             The options available for this field as defined in the L<< /DESCRIPTION
541             >> above.
542              
543             =head2 options_method
544              
545             Coderef of method to return options
546              
547             =head2 multiple
548              
549             If true allows multiple input values
550              
551             =head2 size
552              
553             This can be used to store how many items should be offered in the UI
554             at a given time. Defaults to 0.
555              
556             =head2 empty_select
557              
558             Set to the string value of the select label if you want the renderer
559             to create an empty select value. This only affects rendering - it does
560             not add an entry to the list of options.
561              
562             has_field 'fruit' => ( type => 'Select',
563             empty_select => '---Choose a Fruit---' );
564              
565             =head2 value_when_empty
566              
567             Usually the empty value is an empty arrayref. This attribute allows
568             changing that. Used by SelectCSV field.
569              
570             =head2 label_column
571              
572             Sets or returns the name of the method to call on the foreign class
573             to fetch the text to use for the select list.
574              
575             Refers to the method (or column) name to use in a related
576             object class for the label for select lists.
577              
578             Defaults to "name".
579              
580             =head2 localize_labels
581              
582             For the renderers: whether or not to call the localize method on the select
583             labels. Default is off.
584              
585             =head2 active_column
586              
587             Sets or returns the name of a boolean column that is used as a flag to indicate that
588             a row is active or not. Rows that are not active are ignored.
589              
590             The default is "active".
591              
592             If this column exists on the class then the list of options will included only
593             rows that are marked "active".
594              
595             The exception is any columns that are marked inactive, but are also part of the
596             input data will be included with brackets around the label. This allows
597             updating records that might have data that is now considered inactive.
598              
599             =head2 auto_widget_size
600              
601             This is a way to provide a hint as to when to automatically
602             select the widget to display for fields with a small number of options.
603             For example, this can be used to decided to display a radio select for
604             select lists smaller than the size specified.
605              
606             See L<select_widget> below.
607              
608             =head2 sort_column
609              
610             Sets or returns the column or arrayref of columns used in the foreign class
611             for sorting the options labels. Default is undefined.
612              
613             If not defined the label_column is used as the sort condition.
614              
615             =head2 select_widget
616              
617             If the widget is 'select' for the field then will look if the field
618             also has a L<auto_widget_size>. If the options list is less than or equal
619             to the L<auto_widget_size> then will return C<radio_group> if L<multiple> is false,
620             otherwise will return C<checkbox_group>.
621              
622             =head2 as_label
623              
624             Returns the option label for the option value that matches the field's current value.
625             Can be helpful for displaying information about the field in a more friendly format.
626              
627             =head2 no_option_validation
628              
629             Set this flag to true if you don't want to validate the options that are submitted.
630             This would generally only happen if the options are generated via javascript.
631              
632             =head2 error messages
633              
634             Customize 'select_invalid_value' and 'select_not_multiple'. Though neither of these
635             messages should really be seen by users in a properly constructed select.
636              
637             =head1 Rendering
638              
639             The 'select' field can be rendered by the 'Select', 'RadioGroup', and 'CheckboxGroup'
640             widgets. 'RadioGroup' is for a single select, and 'CheckboxGroup' is for a multiple
641             select.
642              
643             Option groups can be rendered by providing an options arrays with 'group' elements
644             containing options:
645              
646             sub options_testop { (
647             {
648             group => 'First Group',
649             options => [
650             { value => 1, label => 'One' },
651             { value => 2, label => 'Two' },
652             { value => 3, label => 'Three' },
653             ],
654             },
655             {
656             group => 'Second Group',
657             options => [
658             { value => 4, label => 'Four' },
659             { value => 5, label => 'Five' },
660             { value => 6, label => 'Six' },
661             ],
662             },
663             ) }
664              
665             The select rendering widgets all have a 'render_option' method, which may be useful
666             for situations when you want to split up the rendering of a radio group or checkbox group.
667              
668             =head2 Rendering with Template Toolkit
669              
670             Calling 'options' from Template Toolkit can causes issues with wantarray when there
671             is only a single option. As a solution you should use 'options_ref'.
672              
673             =head1 Database relations
674              
675             Also see L<HTML::FormHandler::TraitFor::Model::DBIC>.
676              
677             The single select is for a DBIC 'belongs_to' relation. The multiple select is for
678             a 'many_to_many' relation.
679              
680             There is very limited ability to do multiple select with 'has_many' relations.
681             It will only work in very specific circumstances, and requires setting
682             the 'has_many' attribute to the name of the primary key of the related table.
683             This is a somewhat peculiar data structure for a relational database, and may
684             not be what you really want. A 'has_many' is usually represented with a Repeatable
685             field, and may require custom code if the form structure doesn't match the database
686             structure. See L<HTML::FormHandler::Manual::Cookbook>.
687              
688             =head1 AUTHOR
689              
690             FormHandler Contributors - see HTML::FormHandler
691              
692             =head1 COPYRIGHT AND LICENSE
693              
694             This software is copyright (c) 2017 by Gerda Shank.
695              
696             This is free software; you can redistribute it and/or modify it under
697             the same terms as the Perl 5 programming language system itself.
698              
699             =cut