Skip to content

Commit

Permalink
allow limited use of has_many multiple select
Browse files Browse the repository at this point in the history
  • Loading branch information
gshank committed Mar 5, 2011
1 parent 0d38425 commit 104e4f1
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 14 deletions.
17 changes: 7 additions & 10 deletions lib/HTML/FormHandler/Field/Multiple.pm
Expand Up @@ -11,18 +11,15 @@ has '+size' => ( default => 5 );
sub sort_options {
my ( $self, $options ) = @_;

my $value = $self->value;
return $options unless scalar @$options && defined $self->value;
my $value = $self->deflate($self->value);
return $options unless scalar @$value;
# This places the currently selected options at the top of the list
# Makes the drop down lists a bit nicer
if ( @$options && defined $value ) {
my %selected = map { $_ => 1 } ref($value) eq 'ARRAY' ? @$value : ($value);

my @out = grep { $selected{ $_->{value} } } @$options;
push @out, grep { !$selected{ $_->{value} } } @$options;

return \@out;
}
return $options;
my %selected = map { $_ => 1 } @$value;
my @out = grep { $selected{ $_->{value} } } @$options;
push @out, grep { !$selected{ $_->{value} } } @$options;
return \@out;
}

=head1 DESCRIPTION
Expand Down
48 changes: 45 additions & 3 deletions lib/HTML/FormHandler/Field/Select.pm
Expand Up @@ -222,6 +222,21 @@ This does a string compare.
Customize 'select_invalid_value' and 'select_not_multiple'. Though neither of these
messages should really be seen by users in a properly constructed select.
=head1 Database relations
Also see L<HTML::FormHandler::TraitFor::Model::DBIC>.
The single select is for a DBIC 'belongs_to' relation. The multiple select is for
a 'many_to_many' relation.
There is very limited ability to do multiple select with 'has_many' relations.
It will only work in very specific circumstances, and requires setting
the 'has_many' attribute to the name of the primary key of the related table.
This is a somewhat peculiar data structure for a relational database, and may
not be what you really want. A 'has_many' is usually represented with a Repeatable
field, and may require custom code if the form structure doesn't match the database
structure. See L<HTML::FormHandler::Manual::Cookbook>.
=cut

has 'options' => (
Expand Down Expand Up @@ -285,6 +300,9 @@ sub _form_options {
}

has 'multiple' => ( isa => 'Bool', is => 'rw', default => '0' );
# following is for unusual case where a multiple select is a has_many type relation
has 'has_many' => ( isa => 'Str', is => 'rw' );
has '+deflate_to' => ( default => 'fif' );
has 'size' => ( isa => 'Int|Undef', is => 'rw' );
has 'label_column' => ( isa => 'Str', is => 'rw', default => 'name' );
has 'localize_labels' => ( isa => 'Bool', is => 'rw' );
Expand Down Expand Up @@ -359,6 +377,9 @@ sub _inner_validate_field {

# create a lookup hash
my %options = map { $_->{value} => 1 } @{ $self->options };
if( $self->has_many ) {
$value = [map { $_->{$self->has_many} } @$value];
}
for my $value ( ref $value eq 'ARRAY' ? @$value : ($value) ) {
unless ( $options{$value} ) {
$self->add_error($self->get_message('select_invalid_value'), $value);
Expand Down Expand Up @@ -438,13 +459,34 @@ sub _load_options {
sub sort_options { shift; return shift; }

before 'value' => sub {
my $self = shift;
my $self = shift;

my $value = $self->result->value;
if( $self->multiple && (!defined $value || $value eq '') ) {
$self->_set_value([]);

if( $self->multiple ) {
if ( !defined $value || $value eq '' ) {
$self->_set_value( [] );
}
elsif ( $self->has_many && scalar @$value && ref($value->[0]) ne 'HASH' ) {
my @new_values;
foreach my $ele (@$value) {
push @new_values, { $self->has_many => $ele };
}
$self->_set_value( \@new_values );
}
}
};

sub deflate {
my ( $self, $value ) = @_;

return $value unless ( $self->has_many && $self->multiple );

# the following is for the edge case of a has_many select
return $value unless ref($value) eq 'ARRAY' && scalar @$value && ref($value->[0]) eq 'HASH';
return [map { $_->{$self->has_many} } @$value];
}

__PACKAGE__->meta->make_immutable;
use namespace::autoclean;
1;
8 changes: 7 additions & 1 deletion lib/HTML/FormHandler/InitResult.pm
Expand Up @@ -116,7 +116,13 @@ sub _get_value {
if( $field->_can_deflate && $field->deflate_to eq 'value' ) {
@values = $field->_apply_deflation(@values);
}
my $value = @values > 1 ? \@values : shift @values;
my $value;
if( $field->has_flag('multiple')) {
$value = scalar @values == 1 && ! defined $values[0] ? [] : \@values;
}
else {
$value = @values > 1 ? \@values : shift @values;
}
return $value;
}

Expand Down
29 changes: 29 additions & 0 deletions t/form_options.t
Expand Up @@ -165,4 +165,33 @@ is_deeply( $form->fif, { my_list => 2 }, 'fif is correct' );
$rendered_field = $form->field('my_list')->render;
like( $rendered_field, qr/<option value="2" id="my_list\.1" selected="selected">/, 'element is selected' );

# following test is for 'has_many' select field flag
{
package Test::HasMany;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';

has_field 'foo' => ( default => 'my_foo' );
has_field 'hm_bar' => ( type => 'Multiple',
has_many => 'my_id', default => [3] );

sub options_hm_bar { [1, 2, 3, 4] }
}
$form = Test::HasMany->new;
ok( $form, 'has many form built' );
$form->process( params => {} );
my $fif_expected = { foo => 'my_foo', hm_bar => [3] };
is_deeply( $form->fif, $fif_expected, 'got expected fif' );
$form->process( params => { foo => 'my_foo', hm_bar => [4] } );
my $val_expected = { foo => 'my_foo', hm_bar => [ { my_id => 4 } ] };
is_deeply( $form->value, $val_expected, 'got expected value' );
$fif_expected = { foo => 'my_foo', hm_bar => [4] };
is_deeply( $form->fif, $fif_expected, 'got expected fif' );
$form->process( params => { foo => 'my_foo', hm_bar => [1,2] } );
$fif_expected = { foo => 'my_foo', hm_bar => [1,2] };
is_deeply( $form->fif, $fif_expected, 'got expected fif again' );
$val_expected = { foo => 'my_foo', hm_bar => [ { my_id => 1 }, { my_id => 2 } ] };
is_deeply( $form->value, $val_expected, 'got expected value agina' );


done_testing;

0 comments on commit 104e4f1

Please sign in to comment.