root/lang/perl/HTML-AutoForm/trunk/lib/HTML/AutoForm.pm @ 25405

Revision 25405, 6.8 kB (checked in by kazuho, 4 years ago)

remove japanese messages from core, add placeholder in pod for load_locale

Line 
1package HTML::AutoForm;
2
3use strict;
4use warnings;
5use utf8;
6use Scalar::Util;
7
8use Class::Accessor::Lite;
9use HTML::AutoForm::Error;
10use HTML::AutoForm::Field;
11use HTML::AutoForm::Field::AnyText;
12use HTML::AutoForm::Field::InputCheckable;
13use HTML::AutoForm::Field::InputSet;
14use HTML::AutoForm::Field::Checkbox;
15use HTML::AutoForm::Field::Hidden;
16use HTML::AutoForm::Field::Radio;
17use HTML::AutoForm::Field::Option;
18use HTML::AutoForm::Field::Password;
19use HTML::AutoForm::Field::Select;
20use HTML::AutoForm::Field::Text;
21use HTML::AutoForm::Field::Textarea;
22
23our $VERSION;
24our %Defaults;
25our $DEFAULT_LANG;
26
27BEGIN {
28    $VERSION = '0.01';
29    %Defaults = (
30        secure => 1,
31        action => undef,
32        fields => undef, # need to be copied
33    );
34    Class::Accessor::Lite->mk_accessors(keys %Defaults);
35    $DEFAULT_LANG = 'en';
36};
37
38sub load_locale {
39    my $klass = shift;
40    my $locale = shift;
41    require "HTML/AutoForm/Error/${locale}.pm";
42}
43
44sub new {
45    my $klass = shift;
46    my %args = @_ == 1 ? %{$_[0]} : @_;
47    my $fields = delete $args{fields} || [];
48    for my $n qw(action) {
49        die 'mandatory attribute "' . $n . '" is missing'
50            unless defined $args{$n};
51    }
52    my $self = bless {
53        %Defaults,
54        %args,
55        fields => [], # filled afterwards
56    }, $klass;
57    die 'fields should be supplied in: tag => attributes style'
58        unless @$fields % 2 == 0;
59    for (my $i = 0; $i < @$fields; $i += 2) {
60        my $name = $fields->[$i];
61        my $opts = $fields->[$i + 1];
62        die 'field type is missing or invalid'
63            unless $opts->{type} =~ /^(text|hidden|password|radio|select|checkbox|textarea)$/;
64        my $field_klass = 'HTML::AutoForm::Field::' . ucfirst $opts->{type};
65        push @{$self->{fields}}, $field_klass->new(
66            %$opts,
67            name => $name,
68        );
69    }
70    $self;
71}
72
73sub field {
74    my ($self, $n) = @_;
75    for my $f (@{$self->{fields}}) {
76        return $f
77            if $f->name eq $n;
78    }
79    return;
80}
81
82# the default renderer
83sub render {
84    my ($self, $app) = @_;
85    my $query = $app->query;
86   
87    my $do_validate = $query->request_method eq 'POST'
88            || (! $self->secure && %{$query->Vars});
89   
90    my $html = join(
91        '',
92        '<form action="',
93        _escape_html($self->action),
94        '"',
95        ($self->secure ? ' method="POST"' : ''),
96        '>',
97        '<table class="autoform_table">',
98        (map {
99            sub {
100                my $field = shift;
101                my @values = $query->param($_->name);
102                if ($field->type eq 'hidden') {
103                    return ${$_->render(\@values)};
104                }
105                my @r = (
106                    '<tr><th>',
107                    _escape_html($field->label),
108                    '</th><td>',
109                    ${$field->render(\@values)},
110                );
111                if ($do_validate) {
112                    print STDERR "validating: ", $field->name, "\n";
113                    if (my $err = $field->validate(\@values)) {
114                        push(
115                            @r,
116                            '<div class="autoform_error">',
117                            _escape_html('※' . $err->message),
118                            '</div>',
119                        );
120                    }
121                }
122                push @r, '</td></tr>';
123                @r;
124            }->($_)
125        } @{$self->{fields}}),
126        $self->secure
127            ? (
128                '<input type="hidden" name="__nanoa_csrf_key" value="',
129                # TODO: use a different id
130                _escape_html($app->session->session_id),
131                '" />',
132            ) : (),
133        '<tr><th></th><td><input type="submit" value="投稿する" /></td></tr>',
134        '</table></form>',
135    );
136    $html;
137}
138
139sub validate {
140    my ($self, $app) = @_;
141    my $query = $app->query;
142   
143    for my $f (@{$self->{fields}}) {
144        if (my $error = $f->validate($query)) {
145            return;
146        } elsif (my $h = $f->custom) {
147            if (my $error = $h->($f, $query)) {
148                return;
149            }
150        }
151    }
152    if ($self->secure) {
153        my $ok;
154        if (my $csrf_key = $query->param('__nanoa_csrf_key')) {
155            if ($csrf_key eq $app->session->session_id) {
156                $ok = 1;
157            }
158        }
159        return
160            unless $ok;
161    }
162    1;
163}
164
165sub _build_element {
166    my ($tag, $base, $extra, $omit, $append) = @_;
167    my %attr = (
168        (map {
169            ($_ => $base->{$_})
170        } grep {
171            ! exists $omit->{$_} && ! /^(allow_multiple|label|required)$/
172        } keys %$base),
173        %$extra,
174    );
175    my $html = join(
176        '',
177        '<' . $tag,
178        (map {
179            ' ' . $_ . '="' . _escape_html($attr{$_}) . '"'
180        } sort grep {
181            defined $attr{$_}
182        } keys %attr),
183        defined $append ? ('>', $append, '</', $tag, '>') : ' />',
184    );
185    $html;
186}
187
188sub _escape_html {
189    my $str = shift;
190    $str =~ s/&/&amp;/g;
191    $str =~ s/>/&gt;/g;
192    $str =~ s/</&lt;/g;
193    $str =~ s/"/&quot;/g;
194    $str =~ s/'/&#39;/g;
195    $str;
196}
197
1981;
199
200__END__
201
202=head1 NAME
203
204HTML::AutoForm - a standalone HTML form validator and renderer
205
206=head1 SYNOPSIS
207
208    # build form object
209    my $form = HTML::AutoForm->new(
210        fields => [
211            username => {
212                type       => 'text',
213                required   => 1,
214                min_length => 6,
215                max_length => 8,
216                regexp     => qr/^[0-9a-z_]+$/,
217            },
218            password => {
219                type       => 'password',
220                required   => 1,
221                min_length => 8,
222                regexp     => qr/^[0-9A-Za-z_-]+$/,
223            },
224            password2 => {
225                type       => 'password',
226                label      => 'Password (repeat)',
227                required   => 1,
228                validate   => sub {
229                    my $q = shift;
230                    $q->param('password') eq $q->param('password2');
231               },
232            },
233            sex  => {
234                type       => 'radio',
235                options    => [
236                    male   => {},
237                    female => {},
238                },
239                required   => undef,
240            },
241            country => {
242                type       => 'select',
243                options    => [ qw/Australia Japan .../, ],
244                required   => 1,
245            },
246            # add more
247        ],
248    );
249
250=head1 DESCRIPTION
251
252HTML::AutoForm is a simple form validator and renderer.
253
254=head1 METHODS
255
256=head2 new
257
258=head2 secure
259
260=head2 action
261
262=head2 fields
263
264=head2 field
265
266=head2 render
267
268=head2 validate
269
270=head2 load_locale
271
272=head1 AUTHOR
273
274Kazuho Oku E<lt>kazuhooku !@#$%^&* gmail.comE<gt>
275
276=head1 LICENSE
277
278This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
279
280=cut
Note: See TracBrowser for help on using the browser.