HTML::FormFu::Model::HashRef - handle hashrefs


HTML-FormFu documentation Contained in the HTML-FormFu distribution.

Index


Code Index:

NAME

Top

HTML::FormFu::Model::HashRef - handle hashrefs

SYNOPSIS

Top

  ---
    elements:
      - user_id
      - user_name
      - type: Repeatable
        nested_name: addresses
        elements:
          - type: Hidden
            name: id
          - street




  $form->model('HashRef')->default_values( {
    user_id => 123,
    user_name => 'Hans',
    addresses => [
      { id => 2,
        street => 'Somewhere' },
      { id => 3,
        street => 'Somewhere Else' }
    ]    
    } );

  $form->default_model('HashRef');
  my $hashref = $form->model->create();

  # $hashref is very much the same as the hashref you passed to default_values()

DESCRIPTION

Top

If you need the content of a formular as hashref or for processing with other modules like JSON you can use this model.

METHODS

Top

create

This method creates a hashref from a filled form. This form can be filled by calling default_values in HTML::FormFu, default_values of any other model class (e. g. HTML::FormFu::Model::DBIC) or by simply submitting the form.

If deflators is true all deflators are processed (defaults to 1).

If options is true the value of all elements which have options like HTML::FormFu::Element::Select will be transformed.

  ---
    elements:
      - type: Select
        name: select
        options:
          - [1, "Foo"]
          - [2, "Bar"]

If the value of select is 1, create will create this hashref:

  { 'select' => { label => 'Foo', value => 1 } }

If there is more than one value selected, an arrayref is created instead:

  { 'select' => [ { label => 'Foo', value => 1 },
                  { label => 'Bar', value => 2 } ] }

If options is false, the output will look like this:

  { 'select' => 1 }

respectively

  { 'select' => [1, 2] }

options is false by default.

To get a flattened hash, you can set /flatten to a true value (defaults to 0). This will generate a hash which uses the nested name of each field as key and the value of this field as hash value. If there is a field which has more than one value, a counter is added. The above example would result in a hash like this using /flatten:

  { 'select_0' => 1,
    'select_1' => 2 }




update

Alias for create.

default_values

Populate a form using a hashref. This hashref has the same format as the output of create. If inflators is true, all inflators will be processed (defaults to 1).

CONFIGURATION

Top

These methods do not return the model object so chaining is not possible!

options

Adds the label of a value to the hashref if the element has options in HTML::FormFu::Element::_Group. See create for an example. Defaults to 0.

flatten

Flattens the hash using Hash::Flatten. See create for an example. Defaults to 0.

deflators

If true, processes deflators in /create. Defaults to 1.

inflators

If true, processes inflators in /default_values. Defaults to 1.

SEE ALSO

Top

HTML::FormFu, Hash::Flatten

AUTHOR

Top

Moritz Onken, onken@houseofdesign.de


HTML-FormFu documentation Contained in the HTML-FormFu distribution.

package HTML::FormFu::Model::HashRef;
use Moose;

extends 'HTML::FormFu::Model';

use Hash::Flatten;
use Scalar::Util qw(blessed);

has flatten => ( is => 'rw' );
has options => ( is => 'rw' );

has _repeatable => ( is => 'rw', traits  => ['Chained'] );
has _multi      => ( is => 'rw', traits  => ['Chained'] );

has deflators => (
    is      => 'rw',
    default => 1,
    lazy    => 1,
    traits  => ['Chained'],
);

has inflators => (
    is      => 'rw',
    default => 1,
    lazy    => 1,
    traits  => ['Chained'],
);

sub default_values {
    my ( $self, $data ) = @_;
    map { $_->default(undef) }
        ( grep { $_->is_field } @{ $self->form->get_all_elements } );
    $self->_default_values( $self->form, $data );
    return $self;
}

sub _default_values {
    my ( $self, $form, $data ) = @_;
    my $elements = $form->get_elements;
    foreach my $element ( @{$elements} ) {
        my $name        = $element->name        || "";
        my $nested_name = $element->nested_name || "";
        $name =~ s/_\d+$// if ($name);
        if ( $element->is_repeatable ) {
            my $value = $data->{$name} || $data->{$nested_name};
            unless ($value) {
                $element->repeat(0);
                map { $element->remove_element($_) }
                    @{ $element->get_elements };
                next;
            }
            my $k = scalar @{$value};
            $element->repeat($k);
            my $childs = $element->get_elements;
            for ( my $i = 0; $i < $k; $i++ ) {
                $self->_default_values( $childs->[$i], $value->[$i] );
            }
        }
        elsif ( $element->is_block && $element->is_field )
        {    # is a Multi element
            ref $data->{$name} eq "HASH"
                ? $self->_default_values( $element, $data->{$name} )
                : $element->default( $data->{$name} );
        }
        elsif ( $element->is_block ) {
            $self->_default_values( $element,
                  $nested_name
                ? $data->{$nested_name}
                : $data );
        }
        else {
            if ( $self->inflators && @{ $element->get_inflators } > 0 ) {
                my @inflators = @{ $element->get_inflators };
                map { $element->default( $_->process( $data->{$name} ) ) }
                    @inflators;
            }
            else {

                $element->default( $data->{$name} );
            }
        }

    }

    return $self;

}

sub update { shift->create(@_) }

sub create {
    my $self = shift;
    if($self->form->submitted) {
        my $input = _escape_hash($self->form->input);
        my $hf = new Hash::Flatten(
            { ArrayDelimiter => '_', HashDelimiter => '.' } );
        $input = _unescape_hash($hf->unflatten($self->form->input));
        $self->default_values( $self->_unfold_repeatable($self->form, $input) );
    }
    $self->form->render_data;
    my $obj = $self->_as_object_get( $self->form );
    if ( $self->flatten ) {
        my $hf = new Hash::Flatten(
            { ArrayDelimiter => '_', HashDelimiter => '.' } );
        $obj = $self->_unfold_repeatable( $self->form, $hf->flatten($obj) );
    }
    return $obj;
}

sub _as_object_get {
    my $self  = shift;
    my $form  = shift;
    my $e     = $form->get_all_elements;
    my $names = {};
    foreach my $element ( @{$e} ) {
        my $name = $element->nested_name;
        next unless $name;
        next if ( $element->type eq "Multi" );
        my $es_name = _escape_name($name);
        if (   $self->options
            && $element->can('_options')
            && @{ $element->_options } > 0 )
        {
            my @options = @{ $element->_options };
            my @values
                = ref $element->default eq "ARRAY"
                ? @{ $element->default }
                : $element->default;
            $names->{$es_name} = [];
            foreach my $value (@values) {
                my @option
                    = grep { defined $value && $_->{value} eq $value } @options;
                unless (@option) {
                    @options = map { @{ $_->{group} || [] } } @options;
                    @option
                        = grep { $_->{value} eq $value } @options;
                }
                my $obj
                    = [ map { { value => $_->{value}, label => $_->{label} } }
                        @option ];

                push( @{ $names->{$es_name} }, $obj->[0] ) if $name;
            }
            $names->{$es_name} = $names->{$es_name}->[0] if scalar @values == 1;
            $names->{$es_name} ||= { value => undef, label => undef };
        }
        elsif ( $element->is_field && $self->deflators ) {
            my $deflators = $element->get_deflators;
            $names->{$es_name} = $element->default
                if ( $element->can('default') );
            map { $names->{$es_name} = $_->deflator( $names->{$es_name} ) }
                @{$deflators};
        }
        else {
            $names->{$es_name} = $element->default
                if ( $element->can('default') );
        }

        if ( blessed $names->{$es_name} ) { delete $names->{$es_name} }
    }

    my $hf = new Hash::Flatten( { ArrayDelimiter => '_' } );

    return $self->_unfold_repeatable( $form,
        $self->flatten ? $names : $hf->unflatten($names) );
}

sub _escape_hash {
    my $hash = shift;
    my $method = shift || \&_escape_name;
    return $hash unless(ref $hash);
    foreach my $k (keys %$hash) {
        my $v = delete $hash->{$k};
        if(ref $v eq 'HASH') {
            $hash->{$method->($k)} = _escape_hash($v, $method);
        } elsif(ref $v eq 'ARRAY') {
            $hash->{$method->($k)} = [ map { _escape_hash($_, $method) } @$v];
        } else {
            $hash->{$method->($k)} = $v;
        }
    }
    return $hash;
}


sub _unescape_hash {
    return _escape_hash(shift, \&_unescape_name);
}

sub _escape_name {
    my $name = shift;
    $name =~ s/_/\\_/g;
    $name =~ s/\\(_\d+(\.|$))/$1/g;
    return $name;
}

sub _unescape_name {
    my $name = shift;
    $name =~ s/\\_/_/g;
    $name =~ s/\\\./\./g;
    return $name;
}

sub _unfold_repeatable {
    my $self = shift;
    my $form = shift;
    my $data = shift;
    return $data unless ( ref $data eq "HASH" );
    my $new = {};

    while ( my ( $k, $v ) = each %{$data} ) {
        my $key = _unescape_name($k);

        if ( $self->get_repeatable($key) ) {
            $new->{$key} = [];
            # iterate over all array elements
            # we ignore the first one (index 0) as it is undef as we start
            # counting the repeated element names with 1 and the automatic
            # from Hash::Flatten assumed 0 as first index while unflattening
            # the parameter names
            # Example:
            # $v    = [
            #           undef,
            #           {
            #             'foo' => 'bar',
            #             'id' => 1
            #           },
            #           {
            #             'foo' => 'baz',
            #             'id' => 2
            #           }
            #         ];
            for ( my $i = 1; $i < @{$v || []}; $i++ ) {

                # process all key value pairs in an array element
                while ( my ( $name, $values ) = each %{$v->[$i]} ) {

                    # add an empty hash to array of unfolded data if not already present
                    push( @{ $new->{$key} }, {} ) unless $new->{$key}->[ $i - 1 ];

                    # store processed values
                    $new->{$key}->[ $i - 1 ]->{$name}
                        = $self->_unfold_repeatable( $form, $values );
                }
            }
        }
        elsif ( $self->get_multi($key) && ref $v eq "ARRAY" ) {
            for ( @{ $v || [] } ) {
                $new->{$key} = $_;
                last if $new->{$key};
            }
        }
        else {
            $new->{$key} = $self->_unfold_repeatable( $form, $v );
        }
    }

    return $new;
}

sub get_multi {
    my $self    = shift;
    my $element = shift;
    unless ( $self->_multi ) {
        my %multis = ();
        my $multis = $self->form->get_all_elements( { type => qr/Multi/ } );
        foreach my $multi ( @{ $multis || [] } ) {
            my @multis;
            map { push( @multis, $_->name ) } @{ $multi->get_elements };
            map { s/_\d+//; $multis{$_} = 1 } @multis;
        }
        $self->_multi( \%multis );
    }
    return $self->_multi->{$element};

}

sub get_repeatable {
    my $self    = shift;
    my $element = shift;
    unless ( $self->_repeatable ) {
        my %rep = ();
        my $rep = $self->form->get_all_elements( { type => qr/Repeatable/ } );
# TODO - Mario Minati 19.05.2009
# use $_->delimiter to split the keys
        foreach my $rep_element (@{ $rep || [] }) {
            my $name = $rep_element->nested_name;
            die "A Repeatable element without a nested_name attribute cannot be handled by Model::HashRef"
                unless $name;
            $name =~ s/_\d+//;
            $rep{$name} = 1;
        }
        $self->_repeatable( \%rep );
    }
    return $self->_repeatable->{$element};

}

__PACKAGE__->meta->make_immutable;

__END__