File Coverage

blib/lib/HTML/Shakan.pm
Criterion Covered Total %
statement 127 127 100.0
branch 30 38 78.9
condition 1 3 33.3
subroutine 30 30 100.0
pod 5 7 71.4
total 193 205 94.1


line stmt bran cond sub pod time code
1             package HTML::Shakan;
2 23     23   339863 use strict;
  23         55  
  23         586  
3 23     23   116 use warnings;
  23         46  
  23         604  
4 23     23   8444 use Mouse 0.9;
  23         487574  
  23         129  
5             our $VERSION = '2.10';
6 23     23   8448 use Carp ();
  23         66  
  23         344  
7 23     23   423 use 5.008001;
  23         72  
8              
9 23     23   9379 use FormValidator::Lite 0.24 'Email', 'URL', 'Date', 'File';
  23         514959  
  23         165  
10 23     23   2283245 use Hash::MultiValue;
  23         39989  
  23         734  
11              
12 23     23   8367 use HTML::Shakan::Renderer::HTML;
  23         2118  
  23         581  
13 23     23   6967 use HTML::Shakan::Filters;
  23         54  
  23         570  
14 23     23   7412 use HTML::Shakan::Widgets::Simple;
  23         75  
  23         641  
15 23     23   8195 use HTML::Shakan::Fields;
  23         63  
  23         1639  
16 23     23   7614 use HTML::Shakan::Field::Input;
  23         1580  
  23         767  
17 23     23   8284 use HTML::Shakan::Field::Date;
  23         1713  
  23         631  
18 23     23   7353 use HTML::Shakan::Field::Choice;
  23         1678  
  23         578  
19 23     23   7563 use HTML::Shakan::Field::File;
  23         1849  
  23         742  
20 23     23   144 use List::MoreUtils 0.22 'uniq';
  23         458  
  23         153  
21              
22             sub import {
23 21     21   5108 HTML::Shakan::Fields->export_to_level(1);
24             }
25              
26             has '_fvl' => (
27             is => 'ro',
28             isa => 'FormValidator::Lite',
29             lazy => 1,
30             handles => [qw/has_error load_function_message get_error_messages is_error is_valid set_error set_message/],
31             default => sub {
32             my $self = shift;
33             $self->params(); # build laziness data
34              
35             FormValidator::Lite->new($self);
36             }
37             );
38              
39             sub BUILD {
40 60     60 1 123 my $self = shift;
41              
42 60         248 my $fvl = $self->_fvl;
43              
44             # simple check
45 60         988 $fvl->check(do {
46 60         96 my @c;
47 60         181 for my $field (@{ $self->fields }) {
  60         202  
48 68         261 push @c, $field->get_constraints();
49             }
50 60         273 @c;
51             });
52              
53             # run custom validation
54 60 100       3459 if (my $cv = $self->custom_validation) {
55 2         6 $cv->( $self );
56             }
57 60         230 for my $field ($self->fields) {
58 68 100       285 if (my $cv = $field->custom_validation) {
59 2         5 $cv->($self, $field);
60             }
61             }
62              
63 60 100       214 if ($fvl->is_valid) {
64 40         497 $self->_inflate_values();
65             } else {
66 20         240 $fvl->set_param_message(
67             $self->_set_error_messages()
68             );
69             }
70             }
71              
72             has custom_validation => (
73             is => 'ro',
74             isa => 'CodeRef',
75             );
76              
77             sub _set_error_messages {
78 20     20   43 my ($self, ) = @_;
79              
80 20         36 my %x;
81 20         58 for my $field ($self->fields) {
82 27   33     150 $x{$field->name} = $field->label || $field->name;
83             }
84 20         106 %x;
85             }
86              
87             sub _inflate_values {
88 40     40   71 my $self = shift;
89              
90             # inflate values
91 40         99 my $params = $self->params;
92 40         66 for my $field (@{ $self->fields }) {
  40         133  
93 41 100       232 if (my $inf = $field->inflator) {
94 1         4 my $v = $params->{$field->name};
95 1 50       3 if (defined $v) {
96 1         4 $params->{$field->name} = $inf->inflate($v);
97             }
98             }
99             }
100             }
101              
102             has 'submitted' => (
103             is => 'ro',
104             isa => 'Bool',
105             lazy => 1,
106             builder => '_build_submitted',
107             );
108             sub _build_submitted {
109 4     4   36 my ($self, ) = @_;
110              
111 4         9 my $r = $self->request;
112             my $submitted_field = (
113             scalar
114 6 100       145 grep { defined $r->param($_) || defined $r->upload($_) }
115             uniq
116 4         11 map { $_->name }
  6         23  
117             $self->fields
118             );
119 4 100       180 return $submitted_field > 0 ? 1 : 0;
120             }
121              
122             sub submitted_and_valid {
123 2     2 1 5 my $self = shift;
124 2 50       14 $self->submitted && $self->is_valid;
125             }
126              
127             has model => (
128             is => 'rw',
129             isa => 'Object',
130             trigger => sub {
131             my ($self, $model) = @_;
132             $model->form($self);
133             $model;
134             },
135             );
136              
137             has renderer => (
138             is => 'rw',
139             isa => 'Object',
140             builder => '_build_renderer',
141             );
142             sub _build_renderer {
143 60     60   26037 HTML::Shakan::Renderer::HTML->new();
144             }
145             sub render {
146 12     12 1 612 my $self = shift;
147 12         68 $self->renderer()->render($self);
148             }
149              
150             sub render_field {
151 1     1 1 7 my ( $self, $name ) = @_;
152 1         3 my ( $field, ) = grep { $_->name eq $name } $self->fields;
  3         9  
153 1 50       4 return unless $field;
154 1         7 return $self->widgets->render( $self, $field );
155             }
156              
157             sub fillin_param {
158 41     41 0 84 my ($self, $key) = @_;
159 41         142 $self->fillin_params->{$key};
160             }
161             has fillin_params => (
162             is => 'ro',
163             isa => 'HashRef',
164             lazy => 1,
165             default => sub {
166             my $self = shift;
167             my $fp = {};
168             for my $name ($self->request->param) {
169             my @v = $self->request->param($name);
170             if (@v) {
171             $fp->{$name} = @v==1 ? $v[0] : \@v;
172             }
173             }
174             $fp;
175             },
176             );
177              
178             has fields => (
179             is => 'ro',
180             isa => 'ArrayRef',
181             required => 1,
182             auto_deref => 1,
183             );
184              
185             has request => (
186             is => 'ro',
187             isa => 'Object',
188             required => 1,
189             );
190              
191             has 'widgets' => (
192             is => 'ro',
193             isa => 'Str',
194             default => 'HTML::Shakan::Widgets::Simple',
195             );
196              
197             has 'params' => (
198             is => 'rw',
199             isa => 'Hash::MultiValue',
200             lazy => 1,
201             builder => '_build_params',
202             );
203              
204             has 'uploads' => (
205             is => 'rw',
206             isa => 'HashRef',
207             default => sub { +{} },
208             );
209             sub upload {
210 2     2 0 117 my ($self, $name) = @_;
211 2         10 $self->uploads->{$name};
212             }
213              
214             # code taken from MooseX::Param and changed a bit
215             sub param {
216 137     137 1 7420 my $self = shift;
217              
218 137         304 my $params = $self->params;
219              
220             # if they want the list of keys ...
221 137 50       340 return $params->keys if scalar @_ == 0;
222              
223             # if they want to fetch a particular key ...
224 137 100       302 if (scalar @_ == 1) {
225 136 100       482 return wantarray ? $params->get_all($_[0]) : $params->get($_[0]);
226             }
227              
228 1 50       5 ( ( scalar @_ % 2 ) == 0 ) || confess "parameter assignment must be an even numbered list";
229              
230 1         4 my %new = @_;
231 1         6 while ( my ( $key, $value ) = each %new ) {
232 1 50       6 my @values = ref $value eq 'ARRAY' ? @$value : ($value);
233 1         6 $self->params->set($key, @values);
234             }
235 1         32 return;
236             }
237              
238             sub _build_params {
239 60     60   110 my $self = shift;
240 60         109 my $params = {};
241 60         110 for my $field (@{$self->fields}) {
  60         208  
242 68 50       530 if ($self->widgets->can('field_filter')) {
243             # e.g. DateField
244 68         313 $self->widgets->field_filter($self, $field, $params);
245             }
246 68 100       346 if ($field->can('field_filter')) {
247             # e.g. FileField
248 5         18 $field->field_filter($self, $params);
249             }
250              
251 68         201 my $name = $field->name;
252              
253 68         250 my @val = $self->request->param($name);
254 68 100       1748 if (@val != 0) {
255 45 50       127 if ( my $filters = $field->{filters} ) {
256 45         96 @val = map { HTML::Shakan::Filters->filter( $filters, $_ ) } @val;
  48         188  
257             }
258 45 100       184 $params->{$name} = @val==1 ? $val[0] : \@val;
259             }
260             }
261              
262 60         320 Hash::MultiValue->from_mixed($params);
263             }
264              
265 23     23   33705 no Mouse;
  23         59  
  23         116  
266             __PACKAGE__->meta->make_immutable;
267             __END__
268              
269             =encoding utf-8
270              
271             =for stopwords shakan edokko login sakan
272              
273             =head1 NAME
274              
275             HTML::Shakan - Form HTML generator/validator
276              
277             =head1 SYNOPSIS
278              
279             use HTML::Shakan;
280              
281             sub form {
282             my $req = shift;
283             HTML::Shakan->new(
284             fields => [ @_ ],
285             request => $req,
286             model => 'DataModel',
287             );
288             }
289             sub edit {
290             my $req = shift;
291             my $row = $model->get('user' => $req->param('id'));
292             my $form = form(
293             $req => (
294             TextField(name => 'name', label => 'Your name', filter => [qw/WhiteSpace/]),
295             EmailField(name => 'email', label => 'Your email'),
296             ),
297             );
298             if ($req->submitted_and_valid) {
299             $form->model->update($row);
300             redirect('edit_thanks');
301             } else {
302             $form->model->fill($row);
303             render(form => $form);
304             }
305             }
306             sub add {
307             my $req = shift;
308             my $form = form(
309             $req => (
310             TextField(name => 'name', label => 'Your name'),
311             EmailField(name => 'email', label => 'Your email'),
312             )
313             );
314             if ($req->submitted_and_valid) {
315             $form->model->insert($model => 'user');
316             redirect('edit_thanks');
317             }
318             render(form => $form);
319             }
320              
321             # in your template
322             <? if ($form->has_error) { ?><div class="error"><?= $form->error_message() ?></div><? } ?>
323             <form method="post" action="add">
324             <?= $form->render() ?>
325             <p><input type="submit" value="add" /></p>
326             </form>
327              
328             =head1 DESCRIPTION
329              
330             HTML::Shakan is yet another form generator.
331              
332             THIS IS BETA.API WILL CHANGE.
333              
334             =head1 ATTRIBUTES
335              
336             =over 4
337              
338             =item C<custom_validation>
339              
340             form 'login' => (
341             fields => [
342             TextField(name => 'login_id'),
343             PasswordField(name => 'login_pw'),
344             ],
345             custom_validation => sub {
346             my $form = shift;
347             if ($form->is_valid && !MyDB->retrieve($form->param('login_id'), $form->param('login_pw'))) {
348             $form->set_error('login' => 'failed');
349             }
350             }
351             );
352              
353             You can set custom validation callback, validates the field set in the form. For example, this is useful for login form.
354              
355             =item C<submitted>
356              
357             Returns true if the form has been submitted.
358              
359             This attribute will return true if a value for any known field name was submitted.
360              
361             =item C<has_error>
362              
363             Return true if request has an error.
364              
365             =item C<submitted_and_valid>
366              
367             Shorthand for C<< $form->submitted && !$form->has_error >>
368              
369             =item C<params>
370              
371             Returns form parameters. It is L<Hash::MultiValue> object.
372              
373             =back
374              
375             =head1 benchmarking
376              
377             form generation
378              
379             Rate formfu shakan shakan_declare
380             formfu 1057/s -- -77% -84%
381             shakan 4695/s 344% -- -31%
382             shakan_declare 6757/s 539% 44% --
383              
384             =head1 What's shakan
385              
386             Shakan is 左官 in Japanese.
387              
388             If you want to know about shakan, please see L<http://ja.wikipedia.org/wiki/%E5%B7%A6%E5%AE%98>
389              
390             左官 should pronounce 'sakan', formally. but, edokko pronounce 左官 as shakan.
391              
392             =head1 METHODS
393              
394             =over 4
395              
396             =item C<< my $html = $shakan->render(); :Str >>
397              
398             Render form.
399              
400             =item C<< $shakan->render_field($name); :Str >>
401              
402             Render partial form named C<<$name>>.
403              
404             =item C<< $shakan->param($key:Str); :Value[s] >>
405              
406             Retrive the value of the key from parameters. It's behaviour is similar to traditional request objects. (ex. CGI, Plack::Request)
407             That is, it returns single scalar at scalar context and returns array at array context.
408              
409             =back
410              
411             =head1 AUTHOR
412              
413             Tokuhiro Matsuno E<lt>tokuhirom @ gmail.comE<gt>
414              
415             =head1 SEE ALSO
416              
417             L<HTML::FormFu>
418              
419             ToscaWidgets
420              
421             =head1 LICENSE
422              
423             This library is free software; you can redistribute it and/or modify
424             it under the same terms as Perl itself.
425              
426             =cut