line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojolicious::Plugin::NamespaceForm; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Mojolicious::Plugin::NamespaceForm - Support foo.0.bar params |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 VERSION |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
0.01 |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 DESCRIPTION |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
This plugin makes it easier to work with multiple forms on a webpages. |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
This plugins solves the problem related to validation and automatic form |
16
|
|
|
|
|
|
|
filling. That logic is based on the name of the form field, meaning you |
17
|
|
|
|
|
|
|
need to provide unique names for each form of the same type, unless |
18
|
|
|
|
|
|
|
you want confusing error messages displayed to the user. |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=head2 Example |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
The forms below is supposed to illustrate the problem: |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
New product |
26
|
|
|
|
|
|
|
Product name: |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
Existing product |
29
|
|
|
|
|
|
|
Product name: |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=head2 How does it work? |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
This plugin works by wrapping around most of the built in form helpers with |
36
|
|
|
|
|
|
|
extra logic for generating the name of the input field. The helpers below is |
37
|
|
|
|
|
|
|
overridden by default, but the list will probably get longer in the future. |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
check_box |
40
|
|
|
|
|
|
|
hidden_field |
41
|
|
|
|
|
|
|
label_for |
42
|
|
|
|
|
|
|
number_field |
43
|
|
|
|
|
|
|
password_field |
44
|
|
|
|
|
|
|
radio_button |
45
|
|
|
|
|
|
|
select_field |
46
|
|
|
|
|
|
|
text_area |
47
|
|
|
|
|
|
|
text_field |
48
|
|
|
|
|
|
|
url_field |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=head1 SYNOPSIS |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head2 Single object |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
Application/controller logic: |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
use Mojolicious::Lite; |
57
|
|
|
|
|
|
|
plugin 'Mojolicious::Plugin::NamespaceForm'; |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
post '/user' => sub { |
60
|
|
|
|
|
|
|
my $self = shift; |
61
|
|
|
|
|
|
|
my $user = $self->namespace_params('user')->single; |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# $user = { email => '...', name => '...', _index => 42 } |
64
|
|
|
|
|
|
|
}; |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
Template: |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
% stash field_namespace => 'user'; |
69
|
|
|
|
|
|
|
% stash field_index => 42; # optional |
70
|
|
|
|
|
|
|
%= text_field 'email'; |
71
|
|
|
|
|
|
|
%= text_field 'name'; |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
Output: |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=head2 Multiple objects |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
post '/users' => sub { |
81
|
|
|
|
|
|
|
my $self = shift; |
82
|
|
|
|
|
|
|
my @users = @{ $self->namespace_params('user') }; |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# @users = ( |
85
|
|
|
|
|
|
|
# { email => '...', name => '...', _index => 0 }, |
86
|
|
|
|
|
|
|
# { email => '...', name => '...', _index => 1 }, |
87
|
|
|
|
|
|
|
# ); |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
for my $user (@users) { |
90
|
|
|
|
|
|
|
# ... |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
}; |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
Template: |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
% stash field_namespace => 'user'; |
97
|
|
|
|
|
|
|
% stash field_index => 0; |
98
|
|
|
|
|
|
|
% for my $user (@$users) { |
99
|
|
|
|
|
|
|
%= text_field 'email'; |
100
|
|
|
|
|
|
|
%= text_field 'name'; |
101
|
|
|
|
|
|
|
% stash->{field_index}++; |
102
|
|
|
|
|
|
|
% } |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
Output: |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
... |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=cut |
113
|
|
|
|
|
|
|
|
114
|
1
|
|
|
1
|
|
2071
|
use Mojo::Base 'Mojolicious::Plugin'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
8
|
|
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
our $VERSION = '0.01'; |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
my @FORM_HELPERS = qw( |
119
|
|
|
|
|
|
|
check_box |
120
|
|
|
|
|
|
|
hidden_field |
121
|
|
|
|
|
|
|
label_for |
122
|
|
|
|
|
|
|
number_field |
123
|
|
|
|
|
|
|
password_field |
124
|
|
|
|
|
|
|
radio_button |
125
|
|
|
|
|
|
|
select_field |
126
|
|
|
|
|
|
|
text_area |
127
|
|
|
|
|
|
|
text_field |
128
|
|
|
|
|
|
|
url_field |
129
|
|
|
|
|
|
|
); |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=head1 HELPERS |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=head2 namespace_params |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
$obj = $self->namespace_params($namespace); |
136
|
|
|
|
|
|
|
@list_of_hashes = @$obj; |
137
|
|
|
|
|
|
|
$hash_ref = $obj->single; # might die |
138
|
|
|
|
|
|
|
$hash_ref = $obj->get($index); # might return undef |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
The C<$obj> is overloaded in list context: It will return a list of hash-refs |
141
|
|
|
|
|
|
|
ordered by C<_index>. |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
See L for more details. |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=head1 METHODS |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=head2 register |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
$self->register(helpers => [qw( input_tag )]); |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
Will register L and override tag helpers. |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
=cut |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
sub register { |
156
|
1
|
|
|
1
|
1
|
54
|
my($self, $app, $config) = @_; |
157
|
1
|
50
|
|
|
|
7
|
my @helpers = $config->{helpers} ? @{ $config->{helpers} } : @FORM_HELPERS; |
|
0
|
|
|
|
|
0
|
|
158
|
1
|
|
|
|
|
36
|
my $r = $app->renderer; |
159
|
|
|
|
|
|
|
|
160
|
1
|
|
|
|
|
22
|
$app->defaults(field_index => 0, field_namespace => ''); |
161
|
|
|
|
|
|
|
|
162
|
1
|
|
|
|
|
34
|
for my $name (@FORM_HELPERS) { |
163
|
10
|
50
|
|
|
|
582
|
my $original = delete $r->helpers->{$name} or die "No such helper: $name"; |
164
|
|
|
|
|
|
|
$r->add_helper($name => sub { |
165
|
6
|
|
|
6
|
|
106169
|
my($c, $name, @args) = @_; |
166
|
6
|
|
|
|
|
25
|
my $namespace = $c->stash('field_namespace'); |
167
|
6
|
50
|
|
|
|
78
|
return $c->$original($name, @args) unless $namespace; |
168
|
6
|
|
100
|
|
|
19
|
my $index = $c->stash('field_index') || 0; |
169
|
6
|
|
|
|
|
102
|
return $c->$original("$namespace.$index.$name", @args); |
170
|
10
|
|
|
|
|
173
|
}); |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
$r->add_helper(namespace_params => sub { |
174
|
3
|
|
|
3
|
|
111429
|
my($self, $namespace) = @_; |
175
|
3
|
|
|
|
|
19
|
my $validated = $self->validation->output; |
176
|
3
|
|
66
|
|
|
2019
|
my $only_validated = $self->validation->has_error || $self->validation->is_valid; |
177
|
3
|
|
|
|
|
883
|
my %data; |
178
|
|
|
|
|
|
|
|
179
|
3
|
|
|
|
|
48
|
$namespace = qr{^$namespace\.(\d+)\.(.+)$}; |
180
|
|
|
|
|
|
|
|
181
|
3
|
|
|
|
|
19
|
for my $name ($self->param) { |
182
|
5
|
50
|
|
|
|
698
|
next unless $name =~ $namespace; |
183
|
5
|
100
|
100
|
|
|
102
|
next if $only_validated and !defined $validated->{$name}; |
184
|
4
|
|
|
|
|
23
|
$data{$1}{_index} = int $1; |
185
|
4
|
|
66
|
|
|
24
|
$data{$1}{$2} = $validated->{$name} // $self->param($name); |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
|
188
|
3
|
|
|
|
|
348
|
return Mojolicious::Plugin::NamespaceForm::Data->new(data => \%data); |
189
|
1
|
|
|
|
|
44
|
}); |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=head1 NAMESPACE OBJECT METHODS |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=cut |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
package |
197
|
|
|
|
|
|
|
Mojolicious::Plugin::NamespaceForm::Data; |
198
|
|
|
|
|
|
|
|
199
|
1
|
|
|
1
|
|
917
|
use Mojo::Base -base; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
8
|
|
200
|
|
|
|
|
|
|
use overload ( |
201
|
|
|
|
|
|
|
fallback => 1, |
202
|
|
|
|
|
|
|
q(@{}) => sub { |
203
|
|
|
|
|
|
|
[ |
204
|
1
|
|
|
|
|
12
|
sort { $a->{_index} <=> $b->{_index} } |
|
1
|
|
|
|
|
9
|
|
205
|
1
|
|
|
1
|
|
70
|
values %{ $_[0]->{data} } |
206
|
|
|
|
|
|
|
]; |
207
|
|
|
|
|
|
|
}, |
208
|
1
|
|
|
1
|
|
243
|
); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
16
|
|
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=head2 get |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
$hash_ref = $self->get($index); |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
Return a given hash ref by index, or undef if no such index is defined. |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
=cut |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
sub get { |
219
|
0
|
|
|
0
|
|
0
|
$_[0]->{data}{$_[1]}; |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=head2 single |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
$hash_ref = $self->single; |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
This method will die if no data exists for form namespace or if there are more |
227
|
|
|
|
|
|
|
than one item. The index does not matter. |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
=cut |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
sub single { |
232
|
2
|
|
|
2
|
|
32
|
my $self = shift; |
233
|
2
|
|
|
|
|
4
|
my $n = keys %{ $self->{data} }; |
|
2
|
|
|
|
|
65
|
|
234
|
|
|
|
|
|
|
|
235
|
2
|
50
|
|
|
|
8
|
die "No elements in form namespace" unless $n; |
236
|
2
|
50
|
|
|
|
6
|
die "Too many elements in form namespace ($n)" if $n > 1; |
237
|
2
|
|
|
|
|
21
|
values %{ $self->{data} }; |
|
2
|
|
|
|
|
20
|
|
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
=head1 AUTHOR |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
Jan Henning Thorsen - C |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
=cut |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
1; |