File Coverage

blib/lib/Ark/Form.pm
Criterion Covered Total %
statement 119 152 78.2
branch 37 74 50.0
condition 11 21 52.3
subroutine 27 35 77.1
pod 0 22 0.0
total 194 304 63.8


line stmt bran cond sub pod time code
1             package Ark::Form;
2 2     2   12 use utf8;
  2         4  
  2         14  
3 2     2   51 use Mouse;
  2         5  
  2         11  
4              
5 2     2   2147 use Clone 'clone';
  2         7541  
  2         234  
6 2     2   22 use Exporter::AutoClean;
  2         4  
  2         56  
7 2     2   1695 use HTML::Escape ();
  2         2205  
  2         50  
8 2     2   2445 use HTML::Shakan;
  2         375921  
  2         10  
9              
10             extends 'Class::Data::Inheritable';
11              
12             __PACKAGE__->mk_classdata('_fields_data');
13             __PACKAGE__->mk_classdata('_fields_data_order');
14             __PACKAGE__->mk_classdata('_fields_messages');
15             __PACKAGE__->mk_classdata('_widgets_class');
16              
17             has _shakan => (
18             is => 'rw',
19             isa => 'HTML::Shakan',
20             handles => [
21             qw/has_error load_function_message get_error_messages is_error is_valid
22             set_error set_message/, # _shakan->_fvl
23             qw/submitted submitted_and_valid fillin_param fillin_params
24             param params upload uploads widgets/, # _shakan
25             ],
26             );
27              
28             has 'id_tmpl' => (
29             is => 'ro',
30             isa => 'Str',
31             default => 'id_%s',
32             );
33              
34             has context => (
35             is => 'rw',
36             isa => 'Ark::Context',
37             weak_ref => 1,
38             );
39              
40             has request => (
41             is => 'rw',
42             isa => 'Object',
43             required => 1,
44             );
45              
46             has fields => (
47             is => 'ro',
48             isa => 'HashRef',
49             lazy => 1,
50             default => sub {
51             my $self = shift;
52              
53             my $fields = {};
54              
55             for my $name (@{ $self->_fields_data_order }) {
56             my %params = %{ clone $self->_fields_data->{ $name } };
57              
58             my $field;
59             my $type = delete $params{type}
60             or die 'type parameter is required';
61              
62             if (my $cv = delete $params{custom_validation}) {
63             $params{custom_validation} = sub { $cv->($self, @_) };
64             }
65              
66             if (ref $params{choices} eq 'CODE') {
67             $params{choices} = $params{choices}->();
68             }
69              
70             if ($self->needs_localize) {
71             if (my $label = delete $params{label}) {
72             $params{label} = $self->localize($label);
73             }
74              
75             if (my $choices = delete $params{choices}) {
76             $params{choices} = [];
77             while (my ($v, $l) = splice @$choices, 0, 2) {
78             push @{ $params{choices} }, $v, $self->localize($l);
79             }
80             }
81             }
82              
83             if (my ($func) = grep { $type eq $_ } @HTML::Shakan::Fields::EXPORT) {
84             $field = $self->can($func)->(%params);
85             }
86             else {
87             $field = HTML::Shakan::Field::Input->new(
88             type => $type,
89             %params,
90             );
91             }
92              
93             $fields->{ $name } = $field;
94             }
95              
96             $fields;
97             },
98             );
99              
100 2     2   1540 no Mouse;
  2         6  
  2         12  
101              
102             sub EXPORT {
103 2     2 0 8 my ($class, $target) = @_;
104              
105 2         6 my %cloned;
106              
107             Exporter::AutoClean->export(
108             $target,
109             param => sub {
110             # XXX: fix this, need more clean param declation inheritance
111 4 100   4   647 unless ($cloned{$target}++) {
112 2         6 for my $cd (qw/_fields_messages _fields_data _fields_data_order/) {
113 6         166 Class::Data::Inheritable::mk_classdata(
114             $target, $cd, clone $class->$cd,
115             );
116             }
117             }
118 4         73 $target->set_param_data(@_);
119             },
120             widgets => sub {
121 0     0   0 Mouse::load_class($_[0]);
122 0         0 $target->_widgets_class($_[0]);
123             },
124 2         39 );
125              
126             {
127 2     2   707 no strict 'refs';
  2         5  
  2         4102  
  2         101  
128 2         6 *{"$target\::x"} = \&x;
  2         21  
129             }
130             }
131              
132             sub BUILDARGS {
133 10     10 0 201 my ($self, $request, $context) = @_;
134              
135             return {
136 10 50       251 request => $request,
137             $context ? (context => $context) : (),
138             };
139             }
140              
141             sub BUILD {
142 10     10 0 18 my $self = shift;
143 10         49 $self->reset;
144             }
145              
146             sub reset {
147 10     10 0 19 my $self = shift;
148              
149 10         67 my $fields = $self->fields;
150              
151 20         213 $self->_shakan( HTML::Shakan->new(
152             request => $self->request,
153 10         33 fields => [map { $fields->{$_} } @{ $self->_fields_data_order }],
154             $self->can('custom_validation')
155 10 50   10   53 ? (custom_validation => sub { $self->custom_validation(@_) }) : (),
  10 50       11851  
156             $self->_widgets_class
157             ? (widgets => $self->_widgets_class) : (),
158             ));
159             }
160              
161             sub field {
162 11     11 0 19 my ($class, $name, $value) = @_;
163              
164 11 50       31 if ($value) {
165 0         0 $class->fields->{ $name } = $value;
166             }
167              
168 11         68 $class->fields->{ $name };
169             }
170              
171             sub set_param_data {
172 4     4 0 21 my ($self, $name, %params) = @_;
173              
174 4         11 my $overwrite = $name =~ s/^\+//;
175 4         10 my $class = caller(1);
176              
177 4         7 $params{name} = $name;
178              
179 4 100       14 $class->_fields_messages({}) unless $class->_fields_messages;
180 4 100       56 if (my $messages = delete $params{messages}) {
181 2 50       5 for my $func (keys %{ $messages || {} }) {
  2         12  
182 6         46 my $message = $messages->{$func};
183 6         19 $class->_fields_messages->{ "$name.$func" } = $message;
184             }
185             }
186              
187 4 100       27 $class->_fields_data({}) unless $class->_fields_data;
188 4 50       60 if ($overwrite) {
189 0 0       0 my $data = $class->_fields_data->{ $name }
190             or die qq{param "$name" does not defined by parent class};
191              
192 0         0 while (my ($k, $v) = each %params) {
193 0         0 $data->{ $k } = $v;
194             }
195             }
196             else {
197 4   50     25 $params{attr} ||= {};
198 4   33     56 defined $params{$_} and $params{attr}{$_} ||= $params{$_} for qw/id name value/;
      100        
199              
200 4         17 $class->_fields_data->{ $name } = \%params;
201             }
202              
203 4 100       51 $class->_fields_data_order([]) unless $class->_fields_data_order;
204 4         27 push @{ $class->_fields_data_order }, $name
  2         19  
205 4 50       47 unless grep { $_ eq $name } @{ $class->_fields_data_order };
  4         13  
206             }
207              
208             sub label {
209 1     1 0 3 my ($self, $name) = @_;
210              
211 1 50       5 my $field = $self->field($name) or return;
212 1 50       6 my $label = $field->label or return;
213              
214 1 50       11 unless ($field->id) {
215 1         33 $field->id(sprintf($self->id_tmpl, $name));
216             }
217              
218 1         23 sprintf q{},
219             HTML::Escape::escape_html($field->id), HTML::Escape::escape_html($label);
220             }
221              
222             sub input {
223 3     3 0 9 my ($self, $name) = @_;
224              
225 3 50       30 my $field = $self->field($name) or return;
226 3         21 $self->widgets->render( $self, $field );
227             }
228              
229             sub render {
230 1     1 0 4 my ($self, $name) = @_;
231 1 50       4 return $self->_shakan->render unless $name;
232              
233 1   50     17 my $res = ($self->label($name) || '')
      50        
      50        
234             . ($self->input($name) || '')
235             . ($self->error_message($name) || '');
236             }
237              
238             sub valid_param {
239 0     0 0 0 my ($self, $name) = @_;
240 0 0       0 return '' if $self->is_error($name);
241 0 0       0 return defined($self->param($name)) ? $self->param($name) : '';
242             }
243              
244             sub ignore_error {
245 0     0 0 0 my ($self, $form, $name) = @_;
246              
247 0         0 delete $form->_fvl->{_error}{ $name };
248 0         0 @{ $form->_fvl->{_error_ary} } =
  0         0  
249 0         0 grep { $_->[0] ne $name } @{ $form->_fvl->{_error_ary} };
  0         0  
250             }
251              
252             sub needs_localize {
253 27     27 0 42 my $self = shift;
254 27 50       278 $self->context && $self->context->can('localize');
255             }
256              
257             sub localize {
258 0     0 0 0 my $self = shift;
259 0 0       0 return '' if $_[0] eq '';
260 0 0       0 $self->needs_localize && $self->context->localize(@_);
261             }
262              
263             sub error_message_plain {
264 5     5 0 795 my ($self, $name) = @_;
265 5 50       27 return unless $self->is_error($name);
266              
267 10         28 my ($error) =
268 5 50       135 grep { $_->[0] eq $name } @{ $self->_shakan->_fvl->{_error_ary} || [] }
  5 50       37  
269             or return;
270              
271 5         31 $self->_create_error_message($name, lc $error->[1]);
272             }
273              
274             sub error_messages_plain {
275 1     1 0 3 my ($self, $name) = @_;
276 1 50       4 return unless $self->is_error($name);
277              
278 3         8 my (@errors) =
279 1 50       18 grep { $_->[0] eq $name } @{ $self->_shakan->_fvl->{_error_ary} || [] }
  1 50       8  
280             or return;
281              
282 1         3 [map { $self->_create_error_message($name, lc $_->[1]) } @errors];
  2         5  
283             }
284              
285             sub _create_error_message {
286 7     7   16 my ($self, $name, $func) = @_;
287              
288 7         21 my $field = $self->field($name);
289 7 100 33     37 my $label = $field ? $field->label || $field->name : $func;
290              
291 7 50       25 my $messages = {
292 7 50       267 %{ $self->messages || {} },
293 7         12 %{ $self->_fields_messages || {} },
294             };
295              
296 7   66     186 my $message = $messages->{"$name.$func"}
297             || $messages->{ $func };
298              
299 7 50       16 unless ($message) {
300 0         0 warn qq{Message "$name.$func" does not defined};
301 0         0 return;
302             }
303              
304 7 50       20 if ($self->needs_localize) {
305 0         0 $label = $self->localize( $label );
306 0         0 $message = $self->localize( $message, $label );
307             }
308             else {
309             my $gen_msg = sub {
310 7     7   14 my ($tmpl, @args) = @_;
311 7         9 local $_ = $tmpl;
312 7         27 s!\[_(\d+)\]!$args[$1-1]!ge;
  2         13  
313 7         36 $_;
314 7         37 };
315 7         19 $message = $gen_msg->( $message, $label );
316             }
317              
318 7         82 $message;
319             }
320              
321             sub error_message {
322 3     3 0 2090 my ($self, $name) = @_;
323 3 100       17 return unless $self->submitted;
324 2   50     104 sprintf($self->message_format, $self->error_message_plain($name) || return);
325             }
326              
327             sub error_messages {
328 0     0 0 0 my ($self, $name) = @_;
329 0 0       0 [ map { sprintf( $self->message_format, $_ ) }
  0         0  
330 0         0 @{ $self->error_messages_plain($name) || [] } ];
331             }
332              
333             sub fill {
334 0     0 0 0 my $self = shift;
335 0 0       0 my $p = @_ > 1 ? {@_} : $_[0];
336              
337 0         0 for my $k (keys %$p) {
338 0         0 $self->fillin_params->{ $k } = $p->{ $k };
339             }
340             }
341              
342 0     0 0 0 sub x { $_[0] };
343              
344             sub messages {
345 7     7 0 37 my $self = shift;
346              
347             return {
348 105         148 not_null => '[_1] is required',
349 7         27 map({ $_ => '[_1] is invalid' } qw/
350             int ascii date duplication length regex uint
351             http_url
352             email_loose
353             hiragana jtel jzip katakana
354             file_size file_mime
355             / ),
356 7         21 %{ $self->_fields_messages },
357             };
358             }
359              
360             sub message_format {
361 2     2 0 15 '%s';
362             }
363              
364             sub encode_entities {
365 0     0 0   warn 'Ark::Form::encode_entities() is deprecated. use HTML::Escape::escape_html() instead';
366 0           HTML::Escape::escape_html(@_);
367             }
368              
369             __PACKAGE__->meta->make_immutable;