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   13866 use Moo;
  22         28  
  22         128  
6             extends 'Data::MuForm::Field';
7 22     22   5380 use Types::Standard -types;
  22         46005  
  22         190  
8 22     22   64445 use HTML::Entities;
  22         74382  
  22         1781  
9 22     22   9881 use Data::Dump ('pp');
  22         82018  
  22         26607  
10              
11              
12 6     6 0 1368 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 10875 sub build_options {[]}
42 202     202 0 6154 sub has_options { shift->num_options }
43 205     205 0 208 sub num_options { scalar @{$_[0]->options} }
  205         2995  
44 2     2 0 3 sub all_options { @{$_[0]->options} }
  2         47  
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   4744 my ( $self, $value ) = @_;
57 72 100       746 return unless $self->multiple;
58 26 50 33     117 if (!defined $value || $value eq ''){
59 0         0 $value = [];
60             }
61             else {
62 26 50       60 $value = ref $value eq 'ARRAY' ? $value : [$value];
63             }
64 26         352 $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 6634 my $self = shift;
72 47 100       174 if( $self->multiple ) {
73 13         71 $self->not_nullable(1);
74 13         29 return [];
75             }
76             else {
77 34         83 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 986 my $self = shift;
88              
89             # vivify, so predicate works
90 47         416 $self->input_without_param;
91              
92 47 100 66     426 if( $self->options && $self->has_options ) {
93 15         128 $self->options_from('build');
94             }
95 47 100 100     836 if( $self->form && ! exists $self->{methods}->{build_options} ) {
96 42         390 my $suffix = $self->convert_full_name($self->full_name);
97 42         87 my $meth_name = "options_$suffix";
98 42 100       562 if ( my $meth = $self->form->can($meth_name) ) {
99             my $wrap_sub = sub {
100 87     87   76 my $self = shift;
101 87         1188 return $self->form->$meth;
102 24         222 };
103 24         59 $self->{methods}->{build_options} = $wrap_sub;
104             }
105             }
106 47 100       274 $self->_load_options unless $self->has_options;
107             }
108              
109             sub fill_from_params {
110 23     23 0 45 my ( $self, $input, $exists ) = @_;
111 23 100       90 $input = ref $input eq 'ARRAY' ? $input : [$input]
    100          
112             if $self->multiple;
113 23         112 $self->next::method( $input, $exists );
114 23         166 $self->_load_options;
115             }
116              
117             sub fill_from_object {
118 15     15 0 18 my ( $self, $obj ) = @_;
119 15         45 $self->next::method( $obj );
120 15         29 $self->_load_options;
121             }
122              
123             sub fill_from_fields {
124 58     58 0 65 my ( $self ) = @_;
125 58         233 $self->next::method();
126 58         112 $self->_load_options;
127             }
128              
129             sub _load_options {
130 128     128   352 my $self = shift;
131              
132             return
133 128 100 66     566 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       320 if( my $meth = $self->get_method('build_options') ) {
    100          
140 91         152 @options = $meth->($self);
141 91         838 $self->options_from('method');
142             }
143             elsif ( $self->form ) {
144 15         70 my $full_accessor;
145 15 50       205 $full_accessor = $self->parent->full_accessor if $self->parent;
146 15         196 @options = $self->form->lookup_options( $self, $full_accessor );
147 15 50       32 $self->options_from('model') if scalar @options;
148             }
149 108 100       315 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       198 my $opts = ref $options[0] eq 'ARRAY' ? $options[0] : \@options;
159 91         1228 $opts = $self->options($opts); # coerce will re-format
160              
161 91 100       1772 if (scalar @$opts) {
162             # sort options if sort method exists
163 86 50       1256 $opts = $self->sort_options($opts) if $self->methods->{sort};
164             # we don't want to trigger and re-order, so set directly
165 86         809 $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 1262 my $self = shift;
176 7         57 my $args = $self->next::method(@_);
177 7         29 $args->{multiple} = $self->multiple;
178 7         119 $args->{options} = $self->options;
179 7 100       68 $args->{empty_select} = $self->empty_select if $self->has_empty_select;
180 7 50       34 $args->{size} = $self->size if defined $self->size;
181 7         27 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 10 my $self = shift;
192             return {
193 8         762 %{ $self->next::method },
  8         47  
194             %$class_messages,
195             }
196             }
197              
198             sub normalize_input {
199 29     29 0 36 my ($self) = @_;
200              
201 29         56 my $input = $self->input;
202 29 50       66 return unless defined $input; # nothing to check
203              
204 29 100 66     304 if ( ref $input eq 'ARRAY' && !( $self->can('multiple') && $self->multiple ) ) {
    100 100        
      100        
205 2         9 $self->add_error( $self->get_message('select_not_multiple') );
206             }
207             elsif ( ref $input ne 'ARRAY' && $self->multiple ) {
208 1         2 $input = [$input];
209 1         3 $self->input($input);
210             }
211             }
212              
213             sub validate {
214 23     23 1 107 my $self = shift;
215              
216 23 100       75 return if $self->no_option_validation;
217              
218 22         284 my $value = $self->value;
219             # create a lookup hash
220 22         79 my %options;
221 22         24 foreach my $opt ( @{ $self->options } ) {
  22         298  
222 69 100       195 if ( exists $opt->{group} ) {
223 6         4 foreach my $group_opt ( @{ $opt->{options} } ) {
  6         10  
224 18         28 $options{$group_opt->{value}} = 1;
225             }
226             }
227             else {
228 63         109 $options{$opt->{value}} = 1;
229             }
230             }
231 22 100       63 for my $value ( ref $value eq 'ARRAY' ? @$value : ($value) ) {
232 27 100       58 unless ( $options{$value} ) {
233 6         32 my $opt_value = encode_entities($value);
234 6         160 $self->add_error($self->get_message('select_invalid_value'), value => $opt_value);
235 6         406 return;
236             }
237             }
238 16         38 return 1;
239             }
240              
241             sub as_label {
242 2     2 1 20 my ( $self, $value ) = @_;
243              
244 2 100       35 $value = $self->value unless defined $value;
245 2 50       11 return unless defined $value;
246 2 50       10 if ( $self->multiple ) {
247 2 50       8 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         3 my @labels;
259             my %value_hash;
260 2         7 @value_hash{@$value} = ();
261 2         17 for ( $self->all_options ) {
262 8 100       33 if ( exists $value_hash{$_->{value}} ) {
263 4         6 push @labels, $_->{label};
264 4         8 delete $value_hash{$_->{value}};
265 4 100       11 last unless keys %value_hash;
266             }
267             }
268 2         6 my $str = join(', ', @labels);
269 2         11 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.04
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             You can also use an 'attributes' hashref to set additional renderable option
403             attributes (compatible with FH).
404              
405             [{ value => 1, label => 'one', attributes => { 'data-field' => { 'key' => '...' } } }]
406              
407             Note that you should *not* set 'checked' or 'selected' attributes in options.
408             That is handled by setting a field default.
409              
410             has_field 'my_select' => ( type => 'Select', default => 2 );
411              
412             You can also divide the options up into option groups. See the section on
413             rendering.
414              
415             =head2 Reloading options
416              
417             If the options come from the options_<fieldname> method or the database, they
418             will be reloaded every time the form is reloaded because the available options
419             may have changed. To prevent this from happening when the available options are
420             known to be static, set the 'do_not_reload' flag, and the options will not be
421             reloaded after the first time
422              
423             =head2 Sorting options
424              
425             The sorting of the options may be changed using a 'sort_options' method in a
426             custom field class. The 'Multiple' field uses this method to put the already
427             selected options at the top of the list. Note that this won't work with
428             option groups.
429              
430             =head1 Other Attributes and Methods
431              
432             =head2 multiple
433              
434             If true allows multiple input values
435              
436             =head2 size
437              
438             This can be used to store how many items should be offered in the UI
439             at a given time. Defaults to 0.
440              
441             =head2 empty_select
442              
443             Set to the string value of the select label if you want the renderer
444             to create an empty select value. This only affects rendering - it does
445             not add an entry to the list of options.
446              
447             has_field 'fruit' => ( type => 'Select',
448             empty_select => '---Choose a Fruit---' );
449              
450             =head2 label_column
451              
452             Sets or returns the name of the method to call on the foreign class
453             to fetch the text to use for the select list.
454              
455             Refers to the method (or column) name to use in a related
456             object class for the label for select lists.
457              
458             Defaults to "name".
459              
460             =head2 active_column
461              
462             Sets or returns the name of a boolean column that is used as a flag to indicate that
463             a row is active or not. Rows that are not active are ignored.
464              
465             The default is "active".
466              
467             If this column exists on the class then the list of options will included only
468             rows that are marked "active".
469              
470             The exception is any columns that are marked inactive, but are also part of the
471             input data will be included with brackets around the label. This allows
472             updating records that might have data that is now considered inactive.
473              
474             =head2 sort_column
475              
476             Sets or returns the column or arrayref of columns used in the foreign class
477             for sorting the options labels. Default is undefined.
478              
479             If not defined the label_column is used as the sort condition.
480              
481             =head2 as_label
482              
483             Returns the option label for the option value that matches the field's current value.
484             Can be helpful for displaying information about the field in a more friendly format.
485              
486             =head2 no_option_validation
487              
488             Set this flag to true if you don't want to validate the options that are submitted.
489             This would generally only happen if the options are generated via javascript, and
490             you would presumably have some other kind of validation.
491              
492             =head2 error messages
493              
494             Customize 'select_invalid_value' and 'select_not_multiple'. Though neither of these
495             messages should really be seen by users in a properly constructed select.
496              
497             =head1 Rendering
498              
499             The 'select' field can be rendered as a 'select', 'radiogroup', and 'checkboxgroup'.
500             You change the 'layout_type' from 'standard' (for select) to 'radiogroup' or
501             'checkboxgroup' in the render args.
502              
503             Option groups can be rendered by providing an options arrays with 'group' elements
504             containing options:
505              
506             sub options_testop { [
507             {
508             group => 'First Group',
509             options => [
510             { value => 1, label => 'One' },
511             { value => 2, label => 'Two' },
512             { value => 3, label => 'Three' },
513             ],
514             },
515             {
516             group => 'Second Group',
517             options => [
518             { value => 4, label => 'Four' },
519             { value => 5, label => 'Five' },
520             { value => 6, label => 'Six' },
521             ],
522             },
523             ] }
524              
525             You can use the 'render_option' method to render the options individually in
526             a template.
527              
528             =head1 Database relations
529              
530             Also see L<DBIC::MuForm::Role::Model::DBIC>.
531              
532             The single select is for a DBIC 'belongs_to' relation. The multiple select is for
533             a 'many_to_many' relation.
534              
535             =head1 AUTHOR
536              
537             Gerda Shank
538              
539             =head1 COPYRIGHT AND LICENSE
540              
541             This software is copyright (c) 2017 by Gerda Shank.
542              
543             This is free software; you can redistribute it and/or modify it under
544             the same terms as the Perl 5 programming language system itself.
545              
546             =cut