File Coverage

blib/lib/Data/MuForm/Field/Select.pm
Criterion Covered Total %
statement 116 127 91.3
branch 58 72 80.5
condition 18 24 75.0
subroutine 22 23 95.6
pod 3 16 18.7
total 217 262 82.8


line stmt bran cond sub pod time code
1             package Data::MuForm::Field::Select;
2              
3             # ABSTRACT: Select field
4              
5 22     22   13742 use Moo;
  22         31  
  22         116  
6             extends 'Data::MuForm::Field';
7 22     22   5185 use Types::Standard -types;
  22         44718  
  22         197  
8 22     22   64291 use HTML::Entities;
  22         74202  
  22         1638  
9 22     22   9760 use Data::Dump ('pp');
  22         80438  
  22         25899  
10              
11              
12 6     6 0 1253 sub build_form_element { 'select' }
13              
14             has 'options' => (
15             is => 'rw',
16             isa => ArrayRef,
17             lazy => 1,
18             builder => 'build_options',
19             coerce => sub {
20             my $options = shift;
21             my @options = @$options;
22             return [] unless scalar @options;
23             my @opts;
24             my $order = 0;
25             if ( ref $options[0] eq 'HASH' ) {
26             @opts = @options;
27             $_->{order} = $order++ foreach @opts;
28             }
29             elsif ( scalar @options == 1 && ref($options[0]) eq 'ARRAY' ) {
30             @options = @{ $options[0] };
31             push @opts, { value => $_, label => $_, order => $order++ } foreach @options;
32             }
33             else {
34             die "Options array must contain an even number of elements"
35             if @options % 2;
36             push @opts, { value => shift @options, label => shift @options, order => $order++ } while @options;
37             }
38             return \@opts;
39             },
40             );
41 32     32 1 10907 sub build_options {[]}
42 202     202 0 6310 sub has_options { shift->num_options }
43 205     205 0 202 sub num_options { scalar @{$_[0]->options} }
  205         2985  
44 2     2 0 2 sub all_options { @{$_[0]->options} }
  2         28  
45             has 'options_from' => ( is => 'rw', default => 'none' );
46             has 'do_not_reload' => ( is => 'ro' );
47             has 'no_option_validation' => ( is => 'rw' );
48              
49             has 'multiple' => ( is => 'ro', default => 0 );
50             has 'size' => ( is => 'rw' );
51             has 'empty_select' => ( is => 'rw', predicate => 'has_empty_select' );
52              
53             # add trigger to 'value' so we can enforce arrayref value for multiple
54             has '+value' => ( trigger => 1 );
55             sub _trigger_value {
56 72     72   4843 my ( $self, $value ) = @_;
57 72 100       760 return unless $self->multiple;
58 26 50 33     120 if (!defined $value || $value eq ''){
59 0         0 $value = [];
60             }
61             else {
62 26 50       51 $value = ref $value eq 'ARRAY' ? $value : [$value];
63             }
64 26         354 $self->{value} = $value;
65             }
66              
67             # This is necessary because if a Select field is unselected, no param will be
68             # submitted. Needs to be lazy because it checks 'multiple'. Needs to be vivified in BUILD.
69             has '+input_without_param' => ( lazy => 1, builder => 'build_input_without_param' );
70             sub build_input_without_param {
71 47     47 0 6765 my $self = shift;
72 47 100       182 if( $self->multiple ) {
73 13         59 $self->not_nullable(1);
74 13         31 return [];
75             }
76             else {
77 34         77 return '';
78             }
79             }
80              
81             has 'label_column' => ( is => 'rw', default => 'name' );
82             has 'active_column' => ( is => 'rw', default => 'active' );
83             has 'sort_column' => ( is => 'rw' );
84              
85              
86             sub BUILD {
87 47     47 0 1081 my $self = shift;
88              
89             # vivify, so predicate works
90 47         433 $self->input_without_param;
91              
92 47 100 66     414 if( $self->options && $self->has_options ) {
93 15         125 $self->options_from('build');
94             }
95 47 100 100     848 if( $self->form && ! exists $self->{methods}->{build_options} ) {
96 42         427 my $suffix = $self->convert_full_name($self->full_name);
97 42         88 my $meth_name = "options_$suffix";
98 42 100       567 if ( my $meth = $self->form->can($meth_name) ) {
99             my $wrap_sub = sub {
100 87     87   77 my $self = shift;
101 87         1233 return $self->form->$meth;
102 24         234 };
103 24         58 $self->{methods}->{build_options} = $wrap_sub;
104             }
105             }
106 47 100       279 $self->_load_options unless $self->has_options;
107             }
108              
109             sub fill_from_params {
110 23     23 0 164 my ( $self, $input, $exists ) = @_;
111 23 100       99 $input = ref $input eq 'ARRAY' ? $input : [$input]
    100          
112             if $self->multiple;
113 23         77 $self->next::method( $input, $exists );
114 23         87 $self->_load_options;
115             }
116              
117             sub fill_from_object {
118 15     15 0 30 my ( $self, $obj ) = @_;
119 15         46 $self->next::method( $obj );
120 15         36 $self->_load_options;
121             }
122              
123             sub fill_from_fields {
124 58     58 0 73 my ( $self ) = @_;
125 58         232 $self->next::method();
126 58         114 $self->_load_options;
127             }
128              
129             sub _load_options {
130 128     128   342 my $self = shift;
131              
132             return
133 128 100 66     538 if ( $self->options_from eq 'build' ||
      66        
134             ( $self->has_options && $self->do_not_reload ) );
135              
136             # we allow returning an array instead of an arrayref from a build method
137             # and it's the usual thing from the DBIC model
138 108         815 my @options;
139 108 100       324 if( my $meth = $self->get_method('build_options') ) {
    100          
140 91         154 @options = $meth->($self);
141 91         882 $self->options_from('method');
142             }
143             elsif ( $self->form ) {
144 15         67 my $full_accessor;
145 15 50       189 $full_accessor = $self->parent->full_accessor if $self->parent;
146 15         186 @options = $self->form->lookup_options( $self, $full_accessor );
147 15 50       34 $self->options_from('model') if scalar @options;
148             }
149 108 100       298 return unless @options; # so if there isn't an options method and no options
150             # from a table, already set options attributes stays put
151              
152             # possibilities:
153             # @options = ( 1 => 'one', 2 => 'two' );
154             # @options = ({ value => 1, label => 'one', { value => 2, label => 'two'})
155             # @options = ([ 1 => 'one', 2 => 'tw' ]);
156             # @options = ([ { value => 1, label => 'one'}, { value => 2, label => 'two'}]);
157             # @options = ([[ 'one', 'two' ]]);
158 91 100       205 my $opts = ref $options[0] eq 'ARRAY' ? $options[0] : \@options;
159 91         1226 $opts = $self->options($opts); # coerce will re-format
160              
161 91 100       1801 if (scalar @$opts) {
162             # sort options if sort method exists
163 86 50       1287 $opts = $self->sort_options($opts) if $self->methods->{sort};
164             # we don't want to trigger and re-order, so set directly
165 86         818 $self->{options} = $opts;
166             }
167             }
168              
169             our $class_messages = {
170             'select_not_multiple' => 'This field does not take multiple values',
171             'select_invalid_value' => '\'{value}\' is not a valid value',
172             };
173              
174             sub base_render_args {
175 7     7 0 1326 my $self = shift;
176 7         29 my $args = $self->next::method(@_);
177 7         24 $args->{multiple} = $self->multiple;
178 7         117 $args->{options} = $self->options;
179 7 100       76 $args->{empty_select} = $self->empty_select if $self->has_empty_select;
180 7 50       29 $args->{size} = $self->size if defined $self->size;
181 7         28 return $args;
182             }
183              
184             sub render_option {
185 0     0 0 0 my ( $self, $option ) = @_;
186 0         0 my $render_args = $self->get_render_args;
187 0         0 return $self->renderer->render_option($render_args, $option);
188             }
189              
190             sub get_class_messages {
191 8     8 0 13 my $self = shift;
192             return {
193 8         12 %{ $self->next::method },
  8         48  
194             %$class_messages,
195             }
196             }
197              
198             sub normalize_input {
199 29     29 0 38 my ($self) = @_;
200              
201 29         60 my $input = $self->input;
202 29 50       62 return unless defined $input; # nothing to check
203              
204 29 100 66     303 if ( ref $input eq 'ARRAY' && !( $self->can('multiple') && $self->multiple ) ) {
    100 100        
      100        
205 2         11 $self->add_error( $self->get_message('select_not_multiple') );
206             }
207             elsif ( ref $input ne 'ARRAY' && $self->multiple ) {
208 1         1 $input = [$input];
209 1         4 $self->input($input);
210             }
211             }
212              
213             sub validate {
214 23     23 1 105 my $self = shift;
215              
216 23 100       85 return if $self->no_option_validation;
217              
218 22         282 my $value = $self->value;
219             # create a lookup hash
220 22         77 my %options;
221 22         31 foreach my $opt ( @{ $self->options } ) {
  22         295  
222 69 100       202 if ( exists $opt->{group} ) {
223 6         5 foreach my $group_opt ( @{ $opt->{options} } ) {
  6         8  
224 18         29 $options{$group_opt->{value}} = 1;
225             }
226             }
227             else {
228 63         109 $options{$opt->{value}} = 1;
229             }
230             }
231 22 100       65 for my $value ( ref $value eq 'ARRAY' ? @$value : ($value) ) {
232 27 100       60 unless ( $options{$value} ) {
233 6         32 my $opt_value = encode_entities($value);
234 6         114 $self->add_error($self->get_message('select_invalid_value'), value => $opt_value);
235 6         444 return;
236             }
237             }
238 16         35 return 1;
239             }
240              
241             sub as_label {
242 2     2 1 15 my ( $self, $value ) = @_;
243              
244 2 100       18 $value = $self->value unless defined $value;
245 2 50       8 return unless defined $value;
246 2 50       7 if ( $self->multiple ) {
247 2 50       5 unless ( ref($value) eq 'ARRAY' ) {
248 0 0       0 if( $self->has_transform_default_to_value ) {
249 0         0 my @values = $self->transform_default_to_value->($self, $value);
250 0         0 $value = \@values;
251             }
252             else {
253             # not sure under what circumstances this would happen, but
254             # just in case
255 0         0 return $value;
256             }
257             }
258 2         2 my @labels;
259             my %value_hash;
260 2         4 @value_hash{@$value} = ();
261 2         8 for ( $self->all_options ) {
262 8 100       23 if ( exists $value_hash{$_->{value}} ) {
263 4         5 push @labels, $_->{label};
264 4         9 delete $value_hash{$_->{value}};
265 4 100       10 last unless keys %value_hash;
266             }
267             }
268 2         5 my $str = join(', ', @labels);
269 2         8 return $str;
270             }
271             else {
272 0           for ( $self->all_options ) {
273 0 0         return $_->{label} if $_->{value} eq $value;
274             }
275             }
276 0           return;
277             }
278              
279             1;
280              
281             __END__
282              
283             =pod
284              
285             =encoding UTF-8
286              
287             =head1 NAME
288              
289             Data::MuForm::Field::Select - Select field
290              
291             =head1 VERSION
292              
293             version 0.03
294              
295             =head1 DESCRIPTION
296              
297             This is a field that includes a list of possible valid options.
298             This can be used for select and multiple-select fields.
299             The field can be rendered as a select, a checkbox group (when
300             'multiple' is turned on), or a radiogroup.
301              
302             Because select lists and checkbox_groups do not return an HTTP
303             parameter when the entire list is unselected, the Select field
304             must assume that the lack of a param means unselection. So to
305             avoid setting a Select field with the 'skip_fields_without_input'
306             flag, it must be set to inactive, not merely not included in the
307             submitted params.
308              
309             =head2 options
310              
311             The 'options' attribute returns an arrayref. (In FH it used to return
312             an array.)
313              
314             The 'options' array can come from a number of different places, but
315             a coercion will reformat from one of the valid options formats to
316             the standard arrayref of hashrefs.
317              
318             =over 4
319              
320             =item From a field declaration
321              
322             In a field declaration:
323              
324             has_field 'opt_in' => ( type => 'Select',
325             options => [{ value => 0, label => 'No'}, { value => 1, label => 'Yes'} ] );
326              
327             =item From a coderef supplied to the field definition
328              
329             has_field 'flim' => ( type => 'Select', methods => { build_options => \&flim_options );
330             sub flim_options { <return options arrayref> }
331              
332             =item From a form 'options_<field_name>' method or attribute
333              
334             has_field 'fruit' => ( type => 'Select' );
335             sub options_fruit { <returns options arraryef> }
336             OR
337             has 'options_fruit' => ( is => 'rw',
338             default => sub { [1 => 'apples', 2 => 'oranges', 3 => 'kiwi'] } );
339              
340             The 'attribute' version is mostly useful when you want to be able to pass the
341             options in on ->new or ->process.
342              
343             =item From a field class 'build_options' method
344              
345             In a custom field class:
346              
347             package MyApp::Field::WeekDay;
348             use Moo;
349             extends 'Data::MuForm::Field::Select';
350             ....
351             sub build_options { <returns a valid options arrayref> }
352              
353             =item From the database
354              
355             The final source of the options array is a database when the name of the
356             accessor is a relation to the table holding the information used to construct
357             the select list. The primary key is used as the value. The other columns used are:
358              
359             label_column -- Used for the labels in the options (default 'name')
360             active_column -- The name of the column to be used in the query (default 'active')
361             that allows the rows retrieved to be restricted
362             sort_column -- The name or arrayref of names of the column(s) used to sort the options
363              
364             See also L<Data::MuForm::Model::DBIC>, the 'lookup_options' method.
365              
366             =back
367              
368             The options field should contain one of the following valid data structures:
369              
370             =over
371              
372             =item ArrayRef of HashRefs
373              
374             Each hash reference defines an option, with the label and value
375             attributes corresponding to those of the HTML field. This is the only
376             structure in which you can supply additional attributes to be rendered.
377             This is the format to which the other accepted formats will be coerced.
378              
379             [{ value => 1, label => 'one' }, { value => 2, label => 'two' }]]
380              
381             =item ArrayRef
382              
383             A list of key/value pairs corresponding to HTML field values and labels.
384              
385             [ 1 => 'one', 2 => 'two' ]
386              
387             =item ArrayRef containing one ArrayRef
388              
389             Each item inside the inner ArrayRef defines both the label and value of
390             an option.
391              
392             [[ 'one', 'two' ]]
393              
394             =back
395              
396             =head2 Customizing options
397              
398             Additional attributes can be added in the options array hashref.
399              
400             [{ value => 1, label => 'one', id => 'first' }, { value => 1, label => 'two', id => 'second' }]
401              
402             Note that you should *not* set 'checked' or 'selected' attributes in options.
403             That is handled by setting a field default.
404              
405             has_field 'my_select' => ( type => 'Select', default => 2 );
406              
407             You can also divide the options up into option groups. See the section on
408             rendering.
409              
410             =head2 Reloading options
411              
412             If the options come from the options_<fieldname> method or the database, they
413             will be reloaded every time the form is reloaded because the available options
414             may have changed. To prevent this from happening when the available options are
415             known to be static, set the 'do_not_reload' flag, and the options will not be
416             reloaded after the first time
417              
418             =head2 Sorting options
419              
420             The sorting of the options may be changed using a 'sort_options' method in a
421             custom field class. The 'Multiple' field uses this method to put the already
422             selected options at the top of the list. Note that this won't work with
423             option groups.
424              
425             =head1 Other Attributes and Methods
426              
427             =head2 multiple
428              
429             If true allows multiple input values
430              
431             =head2 size
432              
433             This can be used to store how many items should be offered in the UI
434             at a given time. Defaults to 0.
435              
436             =head2 empty_select
437              
438             Set to the string value of the select label if you want the renderer
439             to create an empty select value. This only affects rendering - it does
440             not add an entry to the list of options.
441              
442             has_field 'fruit' => ( type => 'Select',
443             empty_select => '---Choose a Fruit---' );
444              
445             =head2 label_column
446              
447             Sets or returns the name of the method to call on the foreign class
448             to fetch the text to use for the select list.
449              
450             Refers to the method (or column) name to use in a related
451             object class for the label for select lists.
452              
453             Defaults to "name".
454              
455             =head2 active_column
456              
457             Sets or returns the name of a boolean column that is used as a flag to indicate that
458             a row is active or not. Rows that are not active are ignored.
459              
460             The default is "active".
461              
462             If this column exists on the class then the list of options will included only
463             rows that are marked "active".
464              
465             The exception is any columns that are marked inactive, but are also part of the
466             input data will be included with brackets around the label. This allows
467             updating records that might have data that is now considered inactive.
468              
469             =head2 sort_column
470              
471             Sets or returns the column or arrayref of columns used in the foreign class
472             for sorting the options labels. Default is undefined.
473              
474             If not defined the label_column is used as the sort condition.
475              
476             =head2 as_label
477              
478             Returns the option label for the option value that matches the field's current value.
479             Can be helpful for displaying information about the field in a more friendly format.
480              
481             =head2 no_option_validation
482              
483             Set this flag to true if you don't want to validate the options that are submitted.
484             This would generally only happen if the options are generated via javascript, and
485             you would presumably have some other kind of validation.
486              
487             =head2 error messages
488              
489             Customize 'select_invalid_value' and 'select_not_multiple'. Though neither of these
490             messages should really be seen by users in a properly constructed select.
491              
492             =head1 Rendering
493              
494             The 'select' field can be rendered as a 'select', 'radiogroup', and 'checkboxgroup'.
495             You change the 'layout_type' from 'standard' (for select) to 'radiogroup' or
496             'checkboxgroup' in the render args.
497              
498             Option groups can be rendered by providing an options arrays with 'group' elements
499             containing options:
500              
501             sub options_testop { [
502             {
503             group => 'First Group',
504             options => [
505             { value => 1, label => 'One' },
506             { value => 2, label => 'Two' },
507             { value => 3, label => 'Three' },
508             ],
509             },
510             {
511             group => 'Second Group',
512             options => [
513             { value => 4, label => 'Four' },
514             { value => 5, label => 'Five' },
515             { value => 6, label => 'Six' },
516             ],
517             },
518             ] }
519              
520             You can use the 'render_option' method to render the options individually in
521             a template.
522              
523             =head1 Database relations
524              
525             Also see L<DBIC::MuForm::Role::Model::DBIC>.
526              
527             The single select is for a DBIC 'belongs_to' relation. The multiple select is for
528             a 'many_to_many' relation.
529              
530             =head1 AUTHOR
531              
532             Gerda Shank
533              
534             =head1 COPYRIGHT AND LICENSE
535              
536             This software is copyright (c) 2017 by Gerda Shank.
537              
538             This is free software; you can redistribute it and/or modify it under
539             the same terms as the Perl 5 programming language system itself.
540              
541             =cut