File Coverage

blib/lib/Mojolicious/Plugin/FormFields.pm
Criterion Covered Total %
statement 152 156 97.4
branch 36 46 78.2
condition 31 51 60.7
subroutine 33 34 97.0
pod 1 1 100.0
total 253 288 87.8


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::FormFields;
2              
3 12     12   6556 use Mojo::Base 'Mojolicious::Plugin::ParamExpand';
  12         15  
  12         67  
4              
5             our $VERSION = '0.01_02';
6              
7             sub register
8             {
9 12     12 1 471 my ($self, $app, $config) = @_;
10              
11 12         28 $config->{separator} = Mojolicious::Plugin::FormFields::Field->separator;
12 12         65 $self->SUPER::register($app, $config);
13              
14             $app->helper(field => sub {
15 42     42   202012 my $c = shift;
16 42   100     116 my $name = shift || '';
17 42         66 my $key = "formfields.$name";
18 42   100     90 $c->stash->{$key} ||= Mojolicious::Plugin::FormFields::Field->new($c, $name, @_);
19 37         203 $c->stash->{$key};
20 12         647 });
21              
22             $app->helper(fields => sub {
23 7     7   12343 Mojolicious::Plugin::FormFields::Fields->new(@_);
24 12         296 });
25             }
26              
27             package Mojolicious::Plugin::FormFields::Field;
28              
29 12     12   30685 use Mojo::Base '-strict';
  12         14  
  12         57  
30 12     12   625 use Scalar::Util;
  12         11  
  12         454  
31 12     12   46 use Carp ();
  12         13  
  12         282  
32              
33             use overload
34 12         66 '@{}' => '_to_fields',
35             '""' => '_to_string',
36 12     12   40 fallback => 1;
  12         14  
37              
38             my $SEPARATOR = '.';
39              
40             sub new
41             {
42 37     37   417 my $class = shift;
43 37         48 my ($c, $name, $object) = @_;
44 37 100       300 Carp::croak 'field name required' unless $name;
45              
46 36         86 my $self = bless {
47             c => $c,
48             name => $name,
49             object => $object,
50             value => _lookup_value($name, $object, $c)
51             }, $class;
52              
53 32         390 Scalar::Util::weaken $self->{c};
54 32         80 $self;
55             }
56              
57             sub checkbox
58             {
59 2     2   10 my $self = shift;
60              
61 2         2 my $value;
62 2 50       5 $value = shift if @_ % 2;
63 2   50     9 $value //= 1;
64              
65 2         2 my %options = @_;
66 2         9 $options{value} = $value;
67              
68 2         7 $self->input('checkbox', %options);
69             }
70              
71             sub file
72             {
73 2     2   14 my ($self, %options) = @_;
74 2   66     10 $options{id} //= _dom_id($self->{name});
75              
76 2         15 $self->{c}->file_field($self->{name}, %options);
77             }
78              
79             sub input
80             {
81 15     15   29 my ($self, $type, %options) = @_;
82 15         27 my $value = $self->{value};
83              
84 15   66     52 $options{id} //= _dom_id($self->{name});
85 15 50 66     57 $options{value} //= $value if defined $value;
86 15         19 $options{type} = $type;
87              
88 15 100 100     58 if($type eq 'checkbox' || $type eq 'radio') {
89             $options{checked} = 'checked'
90 4 100 33     50 if !exists $options{checked} && defined $value && $value eq $options{value};
      66        
91             }
92              
93 15         102 $self->{c}->input_tag($self->{name}, %options);
94             }
95              
96             sub hidden
97             {
98 4     4   15 my ($self, %options) = @_;
99 4         7 $self->input('hidden', %options);
100             }
101              
102             sub radio
103             {
104 2     2   12 my ($self, $value, %options) = @_;
105 2 50       5 Carp::croak 'value required' unless defined $value;
106              
107 2   33     17 $options{id} //= _dom_id($self->{name}, $value);
108 2         4 $options{value} = $value;
109              
110 2         7 $self->input('radio', %options);
111             }
112              
113             sub select
114             {
115 2     2   12 my $self = shift;
116 2 50       8 my $options = @_ % 2 ? shift : [];
117 2         4 my %attr = @_;
118 2   33     11 $attr{id} //= _dom_id($self->{name});
119              
120 2         4 my $c = $self->{c};
121 2         3 my $name = $self->{name};
122 2         2 my $field;
123              
124 2 50       7 if(defined $c->param($name)) {
125 0         0 $field = $c->select_field($name, $options, %attr);
126             }
127             else {
128             # Make select_field select the value
129 2         79 $c->param($name, $self->{value});
130 2         31 $field = $c->select_field($name, $options, %attr);
131 2         754 $c->param($name, undef);
132             }
133              
134 2         23 $field;
135             }
136              
137             sub password
138             {
139 2     2   15 my ($self, %options) = @_;
140 2   33     10 $options{id} //= _dom_id($self->{name});
141              
142 2         15 $self->{c}->password_field($self->{name}, %options);
143             }
144              
145             sub label
146             {
147 6     6   38 my $self = shift;
148              
149 6         6 my $text;
150 6 100       14 $text = pop if ref $_[-1] eq 'CODE';
151 6 100       13 $text = shift if @_ % 2; # step on CODE
152 6   66     15 $text //= _default_label($self->{name});
153              
154 6         10 my %options = @_;
155 6   33     22 $options{for} //= _dom_id($self->{name});
156              
157 6         35 $self->{c}->tag('label', %options, $text)
158             }
159              
160             sub text
161             {
162 6     6   22 my ($self, %options) = @_;
163 6         15 $self->input('text', %options);
164             }
165              
166             sub textarea
167             {
168 1     1   3 my ($self, %options) = @_;
169 1   33     6 $options{id} //= _dom_id($self->{name});
170              
171 1         1 my $size = delete $options{size};
172 1 50 33     4 if($size && $size =~ /^(\d+)[xX](\d+)$/) {
173 0         0 $options{rows} = $1;
174 0         0 $options{cols} = $2;
175             }
176              
177 1 50   1   9 $self->{c}->text_area($self->{name}, %options, sub { $self->{value} || '' });
  1         76  
178             }
179              
180             sub each
181             {
182 1     1   11 my $self = shift;
183 1         2 my $block = pop;
184 1         2 my $fields = $self->_to_fields;
185              
186 1 50       4 return $fields unless ref($block) eq 'CODE';
187            
188 1         2 local $_;
189 1         4 $block->() for @$fields;
190              
191 1         119 return;
192             }
193              
194 24     24   43 sub separator { $SEPARATOR; }
195              
196 47     47   480 sub _to_string { shift->{value}; }
197              
198             sub _to_fields
199             {
200 4     4   17 my $self = shift;
201 4         4 my $value = $self->{value};
202              
203 4         5 my $fields = [];
204 4 100       21 return $fields unless ref($value) eq 'ARRAY';
205              
206 3         16 my $i = -1;
207 3         9 while(++$i < @$value) {
208 6         14 my $path = "$self->{name}${SEPARATOR}$i";
209 6         30 push @$fields, $self->{c}->fields($path, $self->{object});
210             }
211              
212 3         10 $fields;
213             }
214              
215             sub _dom_id
216             {
217 27     27   34 my @name = @_;
218 27         146 s/[^\w]+/-/g for @name;
219 27         89 join '-', @name;
220             }
221              
222             sub _default_label
223             {
224 2     2   20 my $label = (split /\Q$SEPARATOR/, shift)[-1];
225 2         5 $label =~ s/[^-a-z0-9]+/ /ig;
226 2         31 ucfirst $label;
227             }
228              
229             sub _checked_field
230             {
231 0     0   0 my ($self, $value, $options) = @_;
232             }
233              
234             sub _lookup_value
235             {
236 36     36   45 my ($name, $object, $c) = @_;
237 36         244 my @path = split /\Q$SEPARATOR/, $name;
238              
239 36 100       86 if(!$object) {
240 24         75 $object = $c->stash($path[0]);
241 24 100       210 _invalid_parameter($name, "nothing in the stash for '$path[0]'") unless $object;
242             }
243              
244             # Remove the stash key for $object
245 35         39 shift @path;
246              
247 35         95 while(defined(my $accessor = shift @path)) {
248 42         65 my $isa = ref($object);
249              
250             # We don't handle the case where one of these return an array
251 42 100 100     308 if(Scalar::Util::blessed($object) && $object->can($accessor)) {
    100          
    100          
252 27         81 $object = $object->$accessor;
253             }
254             elsif($isa eq 'HASH') {
255             # If blessed and !can() do we _really_ want to look inside?
256 5         14 $object = $object->{$accessor};
257             }
258             elsif($isa eq 'ARRAY') {
259 8 100       38 _invalid_parameter($name, "non-numeric index '$accessor' used to access an ARRAY")
260             unless $accessor =~ /^\d+$/;
261              
262 7         22 $object = $object->[$accessor];
263             }
264             else {
265 2   100     8 my $type = $isa || 'type that is not a reference';
266 2         9 _invalid_parameter($name, "cannot use '$accessor' on a $type");
267             }
268             }
269              
270 32         238 $object;
271             }
272              
273             sub _invalid_parameter
274             {
275 4     4   9 my ($field, $message) = @_;
276 4         861 Carp::croak "Invalid parameter '$field': $message";
277             }
278              
279             package Mojolicious::Plugin::FormFields::Fields;
280              
281 12     12   21054 use Mojo::Base '-strict';
  12         19  
  12         48  
282 12     12   686 use Carp ();
  12         16  
  12         1172  
283              
284             sub new
285             {
286 7     7   11 my ($class, $c, $name, $object) = @_;
287 7 50       10 Carp::croak 'object name required' unless $name;
288              
289 7         20 my $self = bless {
290             c => $c,
291             name => $name, #path?
292             object => $object
293             }, $class;
294              
295 7         25 Scalar::Util::weaken $self->{c};
296 7         24 $self;
297             }
298              
299             my $sep = Mojolicious::Plugin::FormFields::Field->separator;
300              
301             for my $m (qw(checkbox fields file hidden input label password radio select text textarea)) {
302 12     12   44 no strict 'refs'; *$m = sub {
  12         12  
  12         1462  
303 16     16   2135 my $self = shift;
304 16         16 my $name = shift;
305 16 50       27 Carp::croak 'field name required' unless $name;
306            
307 16         28 my $path = "$self->{name}$sep$name";
308 16         62 my $field = $self->{c}->field($path, $self->{object}, $self->{c});
309              
310 16 100       92 return @{$field} if $m eq 'fields';
  1         2  
311              
312 15         35 $field->$m(@_);
313             };
314             }
315              
316              
317             1;
318              
319             __END__