package Ark::Form; use utf8; use Mouse; use Clone 'clone'; use Exporter::AutoClean; use HTML::Escape (); use HTML::Shakan; extends 'Class::Data::Inheritable'; __PACKAGE__->mk_classdata('_fields_data'); __PACKAGE__->mk_classdata('_fields_data_order'); __PACKAGE__->mk_classdata('_fields_messages'); __PACKAGE__->mk_classdata('_widgets_class'); has _shakan => ( is => 'rw', isa => 'HTML::Shakan', handles => [ qw/has_error load_function_message get_error_messages is_error is_valid set_error set_message/, # _shakan->_fvl qw/submitted submitted_and_valid fillin_param fillin_params param params upload uploads widgets/, # _shakan ], ); has 'id_tmpl' => ( is => 'ro', isa => 'Str', default => 'id_%s', ); has context => ( is => 'rw', isa => 'Ark::Context', weak_ref => 1, ); has request => ( is => 'rw', isa => 'Object', required => 1, ); has fields => ( is => 'ro', isa => 'HashRef', lazy => 1, default => sub { my $self = shift; my $fields = {}; for my $name (@{ $self->_fields_data_order }) { my %params = %{ clone $self->_fields_data->{ $name } }; my $field; my $type = delete $params{type} or die 'type parameter is required'; if (my $cv = delete $params{custom_validation}) { $params{custom_validation} = sub { $cv->($self, @_) }; } if (ref $params{choices} eq 'CODE') { $params{choices} = $params{choices}->(); } if ($self->needs_localize) { if (my $label = delete $params{label}) { $params{label} = $self->localize($label); } if (my $choices = delete $params{choices}) { $params{choices} = []; while (my ($v, $l) = splice @$choices, 0, 2) { push @{ $params{choices} }, $v, $self->localize($l); } } } if (my ($func) = grep { $type eq $_ } @HTML::Shakan::Fields::EXPORT) { $field = $self->can($func)->(%params); } else { $field = HTML::Shakan::Field::Input->new( type => $type, %params, ); } $fields->{ $name } = $field; } $fields; }, ); no Mouse; sub EXPORT { my ($class, $target) = @_; my %cloned; Exporter::AutoClean->export( $target, param => sub { # XXX: fix this, need more clean param declation inheritance unless ($cloned{$target}++) { for my $cd (qw/_fields_messages _fields_data _fields_data_order/) { Class::Data::Inheritable::mk_classdata( $target, $cd, clone $class->$cd, ); } } $target->set_param_data(@_); }, widgets => sub { Mouse::load_class($_[0]); $target->_widgets_class($_[0]); }, ); { no strict 'refs'; *{"$target\::x"} = \&x; } } sub BUILDARGS { my ($self, $request, $context) = @_; return { request => $request, $context ? (context => $context) : (), }; } sub BUILD { my $self = shift; $self->reset; } sub reset { my $self = shift; my $fields = $self->fields; $self->_shakan( HTML::Shakan->new( request => $self->request, fields => [map { $fields->{$_} } @{ $self->_fields_data_order }], $self->can('custom_validation') ? (custom_validation => sub { $self->custom_validation(@_) }) : (), $self->_widgets_class ? (widgets => $self->_widgets_class) : (), )); } sub field { my ($class, $name, $value) = @_; if ($value) { $class->fields->{ $name } = $value; } $class->fields->{ $name }; } sub set_param_data { my ($self, $name, %params) = @_; my $overwrite = $name =~ s/^\+//; my $class = caller(1); $params{name} = $name; $class->_fields_messages({}) unless $class->_fields_messages; if (my $messages = delete $params{messages}) { for my $func (keys %{ $messages || {} }) { my $message = $messages->{$func}; $class->_fields_messages->{ "$name.$func" } = $message; } } $class->_fields_data({}) unless $class->_fields_data; if ($overwrite) { my $data = $class->_fields_data->{ $name } or die qq{param "$name" does not defined by parent class}; while (my ($k, $v) = each %params) { $data->{ $k } = $v; } } else { $params{attr} ||= {}; defined $params{$_} and $params{attr}{$_} ||= $params{$_} for qw/id name value/; $class->_fields_data->{ $name } = \%params; } $class->_fields_data_order([]) unless $class->_fields_data_order; push @{ $class->_fields_data_order }, $name unless grep { $_ eq $name } @{ $class->_fields_data_order }; } sub label { my ($self, $name) = @_; my $field = $self->field($name) or return; my $label = $field->label or return; unless ($field->id) { $field->id(sprintf($self->id_tmpl, $name)); } sprintf q{}, HTML::Escape::escape_html($field->id), HTML::Escape::escape_html($label); } sub input { my ($self, $name) = @_; my $field = $self->field($name) or return; $self->widgets->render( $self, $field ); } sub render { my ($self, $name) = @_; return $self->_shakan->render unless $name; my $res = ($self->label($name) || '') . ($self->input($name) || '') . ($self->error_message($name) || ''); } sub valid_param { my ($self, $name) = @_; return '' if $self->is_error($name); return defined($self->param($name)) ? $self->param($name) : ''; } sub ignore_error { my ($self, $form, $name) = @_; delete $form->_fvl->{_error}{ $name }; @{ $form->_fvl->{_error_ary} } = grep { $_->[0] ne $name } @{ $form->_fvl->{_error_ary} }; } sub needs_localize { my $self = shift; $self->context && $self->context->can('localize'); } sub localize { my $self = shift; return '' if $_[0] eq ''; $self->needs_localize && $self->context->localize(@_); } sub error_message_plain { my ($self, $name) = @_; return unless $self->is_error($name); my ($error) = grep { $_->[0] eq $name } @{ $self->_shakan->_fvl->{_error_ary} || [] } or return; $self->_create_error_message($name, lc $error->[1]); } sub error_messages_plain { my ($self, $name) = @_; return unless $self->is_error($name); my (@errors) = grep { $_->[0] eq $name } @{ $self->_shakan->_fvl->{_error_ary} || [] } or return; [map { $self->_create_error_message($name, lc $_->[1]) } @errors]; } sub _create_error_message { my ($self, $name, $func) = @_; my $field = $self->field($name); my $label = $field ? $field->label || $field->name : $func; my $messages = { %{ $self->messages || {} }, %{ $self->_fields_messages || {} }, }; my $message = $messages->{"$name.$func"} || $messages->{ $func }; unless ($message) { warn qq{Message "$name.$func" does not defined}; return; } if ($self->needs_localize) { $label = $self->localize( $label ); $message = $self->localize( $message, $label ); } else { my $gen_msg = sub { my ($tmpl, @args) = @_; local $_ = $tmpl; s!\[_(\d+)\]!$args[$1-1]!ge; $_; }; $message = $gen_msg->( $message, $label ); } $message; } sub error_message { my ($self, $name) = @_; return unless $self->submitted; sprintf($self->message_format, $self->error_message_plain($name) || return); } sub error_messages { my ($self, $name) = @_; [ map { sprintf( $self->message_format, $_ ) } @{ $self->error_messages_plain($name) || [] } ]; } sub fill { my $self = shift; my $p = @_ > 1 ? {@_} : $_[0]; for my $k (keys %$p) { $self->fillin_params->{ $k } = $p->{ $k }; } } sub x { $_[0] }; sub messages { my $self = shift; return { not_null => '[_1] is required', map({ $_ => '[_1] is invalid' } qw/ int ascii date duplication length regex uint http_url email_loose hiragana jtel jzip katakana file_size file_mime / ), %{ $self->_fields_messages }, }; } sub message_format { '%s'; } sub encode_entities { warn 'Ark::Form::encode_entities() is deprecated. use HTML::Escape::escape_html() instead'; HTML::Escape::escape_html(@_); } __PACKAGE__->meta->make_immutable;