HTML::FormFu::Element::Date - 3 select menu multi-field


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

Index


Code Index:

NAME

Top

HTML::FormFu::Element::Date - 3 select menu multi-field

SYNOPSIS

Top

    ---
    elements:
      - type: Date
        name: birthdate
        label: 'Birthdate:'
        day:
          prefix: "- Day -"
        month:
          prefix: "- Month -"
        year:
          prefix: "- Year -"
          less: 70
          plus: 0
        auto_inflate: 1




DESCRIPTION

Top

Creates a multi element containing 3 select menus for the day, month and year.

A date element named foo would result in 3 select menus with the names foo_day, foo_month and foo_year. The names can instead be overridden by the name value in day, month and year.

This element automatically merges the input parameters from the select menu into a single date parameter (and doesn't delete the individual menu's parameters).

METHODS

Top

default

Arguments: DateTime object

Arguments: $date_string

Accepts either a DateTime object, or a string containing a date, matching the strftime format. Overwrites any default value set in day, month or year.

default_natural

Arguments: $date_string

    - type: Date
      default_natural: 'today'

Accepts a date/time string suitable for passing to parse_datetime in DateTime::Format::Natural.

default_datetime_args

    - type: Date
      default_natural: 'today'
      default_datetime_args:
        set_time_zone: 'Europe/London'

Accepts a hashref of method-names / values that will be called on the default DateTime object, before the select fields' values are set from it.

strftime

Default Value: "%d-%m-%Y"

The format of the date as returned by params in HTML::FormFu, if auto_inflate is not set.

If auto_inflate is used, this is still the format that the parameter will be in prior to the DateTime inflator being run; which is what any Filters and Constraints will receive.

day

Arguments: \%setting

Set values effecting the day select menu. Known keys are:

name

Override the auto-generated name of the select menu.

default

Set the default value of the select menu

prefix

Arguments: $value

Arguments: \@values

A string or arrayref of strings to be inserted into the start of the select menu.

Each value is only used as the label for a select item - the value for each of these items is always the empty string ''.

month

Arguments: \%setting

Set values effecting the month select menu. Known keys are:

name

Override the auto-generated name of the select menu.

default

Set the default value of the select menu

prefix

Arguments: $value

Arguments: \@values

A string or arrayref of strings to be inserted into the start of the select menu.

Each value is only used as the label for a select item - the value for each of these items is always the empty string ''.

names

Arguments: \@months

A list of month names used for the month menu.

If not set, the list of month names is obtained from DateTime::Locale using the locale set in languages in HTML::FormFu.

short_names

Argument: bool

If true (and months is not set) the list of abbreviated month names is obtained from DateTime::Locale using the locale set in languages in HTML::FormFu.

year

Arguments: \%setting

Set values effecting the year select menu. Known keys are:

name

Override the auto-generated name of the select menu.

default

Set the default value of the select menu

prefix

Arguments: $value

Arguments: \@values

A string or arrayref of strings to be inserted into the start of the select menu.

Each value is only used as the label for a select item - the value for each of these items is always the empty string ''.

list

Arguments: \@years

A list of years used for the year menu.

If this is set, reference, less and plus are ignored.

reference

Arguments: $year

Default Value: the current year, calculated from time() (time() in perlfunc)

If list is not set, the list is created from the range of reference - year_less to reference + year_plus.

less

Arguments: $count

Default Value: 0

plus

Arguments: $count

Default Value: 10

reverse

Arguments: bool

Default Value: 0

If true, the list of years is listed in reverse (decreasing) order.

field_order

Arguments: \@fields

Default Value: ['day', 'month', 'year']

Specify the order of the date fields in the rendered HTML.

Not all 3 fields are required. No single field can be used more than once.

auto_inflate

If true, a DateTime Inflator will automatically be added to the element, and it will be given a formatter so that stringification will result in the format specified in strftime.

If you require the DateTime Inflator to have a different stringification format to the format used internally by your Filters and Constraints, then you must explicitly add your own DateTime Inflator, rather than using auto_inflate.

CAVEATS

Top

Although this element inherits from HTML::FormFu::Element::Block, its behaviour for the methods filters|HTML::FormFu/filters in filter (filters|HTML::FormFu/filters in filter), constraints|HTML::FormFu/constraints in constraint (constraints|HTML::FormFu/constraints in constraint), inflators|HTML::FormFu/inflators in inflator (inflators|HTML::FormFu/inflators in inflator), validators|HTML::FormFu/validators in validator (validators|HTML::FormFu/validators in validator) and transformers|HTML::FormFu/transformers in transformer (transformers|HTML::FormFu/transformers in transformer) is more like that of a field element (HTML::FormFu::Element::_Field), meaning all processors are added directly to the date element, not to its select-menu child elements.

This element's get_elements and get_all_elements are inherited from HTML::FormFu::Element::Block, and so have the same behaviour. However, it overrides the get_fields|HTML::FormFu/get_fields method, such that it returns both itself and its child elements.

SEE ALSO

Top

Is a sub-class of, and inherits methods from HTML::FormFu::Element::_Field, HTML::FormFu::Element::Multi, HTML::FormFu::Element::Block, HTML::FormFu::Element

HTML::FormFu

AUTHOR

Top

Carl Franks, cfranks@cpan.org

LICENSE

Top

This library is free software, you can redistribute it and/or modify it under the same terms as Perl itself.


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

package HTML::FormFu::Element::Date;
use Moose;

extends 'HTML::FormFu::Element::Multi';

use HTML::FormFu::Util qw( _filter_components _parse_args );
use DateTime;
use DateTime::Format::Builder;
use DateTime::Format::Natural;
use DateTime::Locale;
use Moose::Util qw( apply_all_roles );
use Scalar::Util qw( blessed );
use List::MoreUtils qw( all none uniq );
use Carp qw( croak );

__PACKAGE__->mk_attrs(qw( day  month  year ));

has auto_inflate          => ( is => 'rw', traits => ['Chained'] );
has default_natural       => ( is => 'rw', traits => ['Chained'] );
has default_datetime_args => ( is => 'rw', traits => ['Chained'] );
has printf_day            => ( is => 'rw', traits => ['Chained'] );
has printf_month          => ( is => 'rw', traits => ['Chained'] );
has printf_year           => ( is => 'rw', traits => ['Chained'] );

has _known_fields => ( is => 'rw' );

has strftime => (
    is      => 'rw',
    default => '%d-%m-%Y',
    lazy    => 1,
    traits  => ['Chained'],
);

*default = \&value;

# build get_Xs methods
for my $method ( qw(
    deflator        filter
    constraint      inflator
    validator       transformer
    ) )
{
    my $sub = sub {
        my $self       = shift;
        my %args       = _parse_args(@_);
        my $get_method = "get_${method}s";

        my $accessor = "_${method}s";
        my @x        = @{ $self->$accessor };
        push @x, map { @{ $_->$get_method(@_) } } @{ $self->_elements };

        return _filter_components( \%args, \@x );
    };

    my $name = __PACKAGE__ . "::get_${method}s";

    no strict 'refs';

    *{$name} = $sub;
}

after BUILD => sub {
    my ( $self, $args ) = @_;

    $self->printf_day(   '%d' );
    $self->printf_month( '%d' );
    $self->printf_year(  '%d' );

    $self->_known_fields( [qw( day month year )] );

    $self->field_order( [qw( day month year )] );

    $self->day( {
            prefix => [],
        } );

    $self->month( {
            prefix => [],
        } );

    $self->year( {
            prefix => [],
            less   => 0,
            plus   => 10,
            reverse => 0,
        } );

    return;
};

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

    if ( @_ > 1 ) {
        $self->{value} = $value;

        # if we're already built - i.e. process() has ben called,
        # call default() on our children

        if ( @{ $self->_elements } ) {
            $self->_date_defaults;

            my @order = @{ $self->field_order };

            for my $i ( 0 .. $#order ) {
                my $field = $order[$i];

                my $printf_method = "printf_$field";

                my $default = $value
                    ? sprintf( $self->$printf_method, $self->$field->{default} )
                    : undef;

                $self->_elements->[$i]->default($default);
            }
        }

        return $self;
    }

    return $self->{value};
}

sub _add_elements {
    my ($self) = @_;

    $self->_elements( [] );

    $self->_date_defaults;

    for my $order ( @{ $self->field_order } ) {
        my $method = "_add_$order";

        $self->$method;
    }

    if ( $self->auto_inflate
        && !@{ $self->get_inflators( { type => "DateTime" } ) } )
    {
        _add_inflator($self);
    }

    return;
}

sub _date_defaults {
    my ($self) = @_;

    my $default;

    if ( defined( $default = $self->default ) && length $default ) {
        
        if ( !$self->form->submitted || $self->render_processed_value ) {
            for my $deflator ( @{ $self->_deflators } ) {
                $default = $deflator->process($default);
            }
        }
        
        my $is_blessed = blessed($default);

        if ( !$is_blessed || ( $is_blessed && !$default->isa('DateTime') ) ) {
            my $builder = DateTime::Format::Builder->new;
            $builder->parser( { strptime => $self->strftime } );

            $default = $builder->parse_datetime($default);
        }
    }
    elsif ( defined( $default = $self->default_natural ) ) {
        my $parser;
        
        if ( defined( my $datetime_args = $self->default_datetime_args ) ) {
            if ( exists $datetime_args->{set_time_zone} ) {
                my $tz = $datetime_args->{set_time_zone};
                $parser = DateTime::Format::Natural->new( time_zone => $tz );
            }
            else {
                $parser = DateTime::Format::Natural->new;
            }
        }
        else {
            $parser = DateTime::Format::Natural->new;
        }
        $default = $parser->parse_datetime( $default );
    }
    else {
      $default = undef;
    }

    if ( defined $default ) {
        
        if ( defined( my $datetime_args = $self->default_datetime_args ) ) {
            for my $key ( keys %$datetime_args ) {
                $default->$key( $datetime_args->{$key} );
            }
        }
        
        for my $field ( @{ $self->field_order } ) {
            $self->$field->{default} = $default->$field;
        }
    }

    return;
}

sub _add_day {
    my ($self) = @_;

    my $day = $self->day;

    my $day_name = $self->_build_name('day');

    my @day_prefix
        = ref $day->{prefix}
        ? @{ $day->{prefix} }
        : $day->{prefix};

    @day_prefix = map { [ '', $_ ] } @day_prefix;

    my $element = $self->element( {
            type    => 'Select',
            name    => $day_name,
            options => [ @day_prefix, map { [ $_, $_ ] } 1 .. 31 ],

            defined $day->{default} ? ( default => $day->{default} ) : (),
        } );

    apply_all_roles( $element, 'HTML::FormFu::Role::Element::MultiElement' );

    return;
}

sub _add_month {
    my ($self) = @_;

    my $month = $self->month;

    my $month_name = $self->_build_name('month');

    my @months = _build_month_list($self);

    my @month_prefix
        = ref $month->{prefix}
        ? @{ $month->{prefix} }
        : $month->{prefix};

    @month_prefix = map { [ '', $_ ] } @month_prefix;

    my $options = [ @month_prefix, map { [ $_ + 1, $months[$_] ] } 0 .. 11 ];

    my $element = $self->element( {
            type    => 'Select',
            name    => $month_name,
            options => $options,

            defined $month->{default} ? ( default => $month->{default} ) : (),
        } );

    apply_all_roles( $element, 'HTML::FormFu::Role::Element::MultiElement' );

    return;
}

sub _add_year {
    my ($self) = @_;

    my $year = $self->year;

    my $year_name = $self->_build_name('year');

    my $year_ref
        = defined $year->{reference}
        ? $year->{reference}
        : ( localtime(time) )[5] + 1900;

    my @years
        = defined $year->{list}
        ? @{ $year->{list} }
        : ( $year_ref - $year->{less} ) .. ( $year_ref + $year->{plus} );

    if ( $year->{reverse} ) {
        @years = reverse(@years);
    }

    my @year_prefix
        = ref $year->{prefix}
        ? @{ $year->{prefix} }
        : $year->{prefix};

    @year_prefix = map { [ '', $_ ] } @year_prefix;

    my $element = $self->element( {
            type    => 'Select',
            name    => $year_name,
            options => [ @year_prefix, map { [ $_, $_ ] } @years ],

            defined $year->{default} ? ( default => $year->{default} ) : (),
        } );

    apply_all_roles( $element, 'HTML::FormFu::Role::Element::MultiElement' );

    return;
}

sub _build_month_list {
    my ($self) = @_;

    my $month = $self->month;
    my @months;

    if ( defined $month->{names} ) {
        @months = @{ $month->{names} };
    }
    else {
        my $languages = $self->form->languages;
        if ( ref $languages ne 'ARRAY' ) {
            $languages = [$languages];
        }

        for my $lang (@$languages) {
            my $loc;

            eval { $loc = DateTime::Locale->load($lang) };
            if ( !$@ ) {
                @months
                    = $month->{short_names}
                    ? @{ $loc->month_format_abbreviated }
                    : @{ $loc->month_format_wide };

                @months = map {ucfirst} @months;

                last;
            }
        }
    }

    return @months;
}

sub _build_number_list {
    my ( $self, $start, $end, $interval ) = @_;
    
    $interval ||= 1;
    
    my @list;
    
    for ( my $i = $start; $i <= $end; $i += $interval ) {
        push @list, $i;
    }
    
    return @list;
}

sub _build_name {
    my ( $self, $type ) = @_;

    my $name
        = defined $self->$type->{name}
        ? $self->$type->{name}
        : sprintf "%s_%s", $self->name, $type;

    return $name;
}

sub _add_inflator {
    my ($self) = @_;

    $self->inflator( {
            type     => "DateTime",
            parser   => { strptime => $self->strftime, },
            strptime => $self->strftime,
        } );

    return;
}

sub field_order {
    my ( $self, @order ) = @_;

    if ( @_ > 1 ) {
        if ( @order == 1 && ref( $order[0] ) eq 'ARRAY' ) {
            @order = @{ $order[0] };
        }

        for my $field (@order) {
            croak "unknown field type: '$field'"
                if none { $field eq $_ } @{ $self->_known_fields };
        }

        croak 'repeated field type'
            if scalar( uniq @order ) != scalar(@order);

        $self->{field_order} = \@order;

        return $self;
    }
    else {
        return $self->{field_order};
    }
}

sub process {
    my ( $self, @args ) = @_;

    $self->_add_elements;

    return $self->SUPER::process(@args);
}

sub process_input {
    my ( $self, $input ) = @_;

    my %value;

    my @order = @{ $self->field_order };

    for my $i ( 0 .. $#order ) {
        my $field = $order[$i];

        my $name = $self->_elements->[$i]->nested_name;

        $value{$field} = $self->get_nested_hash_value( $input, $name );
    }

    if ( ( all {defined} values %value )
        && all {length} values %value )
    {
        my $dt;

        eval {
            $dt = DateTime->new( map { $_, $value{$_} } keys %value );
        };

        my $value;

        if ($@) {
            $value = $self->strftime;
        }
        else {
            $value = $dt->strftime( $self->strftime );
        }

        $self->set_nested_hash_value( $input, $self->nested_name, $value );
    }

    return $self->SUPER::process_input($input);
}

sub render_data {
    return shift->render_data_non_recursive(@_);
}

sub render_data_non_recursive {
    my ( $self, $args ) = @_;

    my $render = $self->SUPER::render_data_non_recursive( {
            elements => [ map { $_->render_data } @{ $self->_elements } ],
            $args ? %$args : (),
        } );

    return $render;
}

__PACKAGE__->meta->make_immutable;

1;

__END__