File Coverage

blib/lib/HTML/FormHandler/Field/Select.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


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