File Coverage

blib/lib/HTML/FormHandlerX/JQueryRemoteValidator.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package HTML::FormHandlerX::JQueryRemoteValidator;
2              
3 1     1   29278 use HTML::FormHandler::Moose::Role;
  0            
  0            
4             use Method::Signatures::Simple;
5             use JSON ();
6              
7             =head1 NAME
8              
9             HTML::FormHandlerX::JQueryRemoteValidator - call server-side validation code asynchronously from client-side forms.
10              
11             =head1 VERSION
12              
13             Version 0.05
14              
15             =cut
16              
17             our $VERSION = '0.05';
18              
19              
20             =head1 SYNOPSIS
21              
22             package MyApp::Form::Foo;
23             use HTML::FormHandler::Moose;
24              
25             with 'HTML::FormHandlerX::JQueryRemoteValidator';
26              
27             ...
28              
29             # You need to provide a form validation script at /ajax/formvalidator
30             # In Poet/Mason, something like this in /ajax/formvalidator/dhandler.mp
31              
32             method handle () {
33             $m->res->content_type('application/json');
34              
35             my ($form_name, $field_name) = split '/', $m->path_info;
36              
37             my $form = $.form($form_name);
38             $form->process(params => $.args, no_update => 1);
39              
40             my $err = join ' ', @{$form->field($field_name)->errors};
41             my $result = $err || 'true';
42              
43             $m->print(JSON->new->allow_nonref->encode($result));
44             }
45              
46              
47             =cut
48              
49             has_field _validation_scripts => (type => 'JavaScript', set_js_code => '_js_code_for_validation_scripts');
50              
51             has validation_endpoint => (is => 'rw', isa => 'Str', default => '/ajax/formvalidator');
52              
53             has jquery_validator_link => (is => 'rw', isa => 'Str', default => 'http://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js');
54              
55             has skip_remote_validation_fields => (is => 'rw', isa => 'ArrayRef', default => sub { [ qw(submit) ] });
56             has skip_remote_validation_types => (is => 'rw', isa => 'ArrayRef', default => sub { [ qw(Hidden noCAPTCHA Display JSON JavaScript) ] });
57              
58             has skip_all_remote_validation => (is => 'rw', isa => 'Bool', default => 0);
59              
60             method _js_code_for_validation_scripts () {
61             my $spec_data = $self->_data_for_validation_spec;
62             my $spec = JSON->new->utf8
63             ->allow_nonref
64             ->pretty(1)
65             # ->relaxed(undef)
66             # ->canonical(undef)
67             ->encode($spec_data)
68             || '';
69              
70             my $form_name = $self->name;
71             $spec =~ s/"${form_name}_data_collector"/${form_name}_data_collector/g;
72             $spec =~ s/\n$//;
73             $spec = "\n var ${form_name}_validation_spec = $spec;\n";
74             return $self->_data_collector_script . $spec . $self->_run_validator_script;
75             }
76              
77             method _data_for_validation_spec () {
78             my $js_profile = { rules => {}, messages => {} };
79              
80             foreach my $field (@{$self->fields}) {
81             next if $self->_skip_remote_validation($field); # don't build rules for these fields
82             $js_profile->{rules}->{$field->id}->{remote} = $self->_build_remote_rule($field);
83             }
84              
85             return $js_profile;
86             }
87              
88             method _build_remote_rule ($field) {
89             my $remote_rule = {
90             url => sprintf("%s/%s/%s", $self->validation_endpoint, $self->name, $field->name),
91             type => 'POST',
92             data => $self->name . "_data_collector",
93             };
94              
95             return $remote_rule;
96             }
97              
98             method _data_collector_script () {
99             my $script = join(",\n",
100             map { sprintf " \"%s.%s\": function () { return \$(\"#%s\\\\.%s\").val() }", $self->name, $_->name, $self->name, $_->name }
101             grep { ! $self->_skip_remote_validation($_) }
102             sort {$a->name cmp $b->name} # the sort is there to keep output consistent for test scripts
103             $self->fields
104             );
105              
106             my $form_name = $self->name;
107             return " var ${form_name}_data_collector = {\n" . $script . "\n };\n";
108             }
109              
110             method _skip_remote_validation ($field) {
111             return 1 if $self->skip_all_remote_validation;
112             my %skip_field = map {$_=>1} @{$self->skip_remote_validation_fields};
113             my %skip_type = map {$_=>1} @{$self->skip_remote_validation_types};
114             return 1 if $skip_field{$field->name};
115             return 1 if $skip_type{$field->type};
116             return 0;
117             }
118              
119             method _run_validator_script () {
120             my $form_name = $self->name;
121             my $link = $self->jquery_validator_link;
122             my $js_apply_error_classes = $self->js_apply_error_classes;
123             my $js_apply_success_classes = $self->js_apply_success_classes;
124             chomp($js_apply_error_classes);
125             chomp($js_apply_success_classes);
126              
127             my $script = <
128              
129             \$(document).ready(function() {
130             \$.getScript("$link", function () {
131             if (typeof ${form_name}_validation_spec !== 'undefined') {
132             \$('form#$form_name').validate({
133             rules: ${form_name}_validation_spec.rules,
134             messages: ${form_name}_validation_spec.messages,
135             highlight: $js_apply_error_classes,
136             success: $js_apply_success_classes,
137             submitHandler: function(form) { form.submit(); }
138             });
139             }
140             });
141             });
142             SCRIPT
143              
144             return $script;
145             }
146              
147             has 'js_apply_error_classes' => (is => 'rw', isa => 'Str', default => <
148             function(element) {
149             \$(element).closest('.form-group').removeClass('success').addClass('error');
150             }
151             JS
152              
153             has 'js_apply_success_classes' => (is => 'rw', isa => 'Str', default => <
154             function(element) {
155             element
156             .text('dummy').addClass('valid')
157             .closest('.form-group').removeClass('error').addClass('success');
158             }
159             JS
160              
161             =head1 CONFIGURATION AND SETUP
162              
163             C adds jQuery scripts to your form to
164             gather form input and send it to the server for validation before the user submits the
165             completed form. The server responds with messages that are displayed on the form.
166             So you will need to set up various bits and pieces. Most have straightforward defaults.
167              
168             =head2 C
169              
170             Default: /ajax/formvalidator
171              
172             The form data will be POSTed to C<[validation_endpoint]/[form_name]/[field_name]>.
173              
174             Note that *all* fields are submitted, not just the field being validated.
175              
176             You must write the code to handle this submission. The response should be a JSON
177             string, either C if the field passed its tests, or a message describing
178             the error. The message will be displayed on the form.
179              
180             The synopsis has an example in Poet/Mason.
181              
182             =head2 C
183              
184             Default: http://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js
185              
186             You can leave this as-is, or if you prefer, you can put the file on your own
187             server and modify this setting to point to it.
188              
189             =head2 C
190              
191             Default:
192              
193             function(element) {
194             \$(element).closest('.form-group').removeClass('success').addClass('error');
195             }
196              
197             JavaScript function called when a field fails validation. The function is passed
198             the form-control element that failed.
199              
200             =head2 C
201              
202             Default:
203              
204             function(element) {
205             element
206             .text('dummy').addClass('valid')
207             .closest('.form-group').removeClass('error').addClass('success');
208             }
209              
210             JavaScript function called when a field passes validation. The function is passed
211             the form-control element that succeeded.
212              
213             =head2 C
214              
215             Default: C<[ qw(Hidden noCAPTCHA Display JSON JavaScript) ]>
216              
217             A list of field types that should not be included in the validation calls.
218              
219             =head2 C
220              
221             Default: C<[ qw(submit) ]>
222              
223             A list of field names that should not be included in the validation calls.
224              
225             =head2 C
226              
227             Boolean, default 0.
228              
229             A flag to turn off remote validation altogether, perhaps useful during form development.
230              
231              
232             =head2 jQuery
233              
234             You need to load the jQuery library yourself. See https://jquery.com/download/
235              
236             =head2 CSS
237              
238             You will probably want to style the C and C classes on your
239             form, for example:
240              
241             label.valid {
242             width: 24px;
243             height: 24px;
244             background: url(/static/images/valid.png) center center no-repeat;
245             display: inline-block;
246             text-indent: -9999px;
247             }
248              
249             label.error {
250             font-weight: normal;
251             color: red;
252             padding: 2px 8px;
253             margin-top: 2px;
254             }
255              
256             =cut
257              
258              
259              
260              
261             =head1 See also
262              
263             =over 4
264              
265             =item L
266              
267             =item L
268              
269             =item L
270              
271             =back
272              
273             =cut
274              
275             =head1 AUTHOR
276              
277             David R. Baird, C<< >>
278              
279             =head1 CODE REPOSITORY
280              
281             L
282              
283             Please report any bugs or feature requests there.
284              
285              
286             =head1 SUPPORT
287              
288             You can find documentation for this module with the perldoc command.
289              
290             perldoc HTML::FormHandlerX::JQueryRemoteValidator
291              
292              
293             You can also look for information at:
294              
295             =over 4
296              
297             =item * MetaCPAN
298              
299             L
300              
301              
302             =item * AnnoCPAN: Annotated CPAN documentation
303              
304             L
305              
306             =item * CPAN Ratings
307              
308             L
309              
310             =item * Search CPAN
311              
312             L
313              
314             =back
315              
316              
317             =head1 ACKNOWLEDGEMENTS
318              
319             This started out as a modification of Aaron Trevana's
320             HTML::FormHandlerX::Form::JQueryValidator
321              
322              
323             =head1 LICENSE AND COPYRIGHT
324              
325             Copyright 2016 David R. Baird.
326              
327             This program is free software; you can redistribute it and/or modify it
328             under the terms of the the Artistic License (2.0). You may obtain a
329             copy of the full license at:
330              
331             L
332              
333             Any use, modification, and distribution of the Standard or Modified
334             Versions is governed by this Artistic License. By using, modifying or
335             distributing the Package, you accept this license. Do not use, modify,
336             or distribute the Package, if you do not accept this license.
337              
338             If your Modified Version has been derived from a Modified Version made
339             by someone other than you, you are nevertheless required to ensure that
340             your Modified Version complies with the requirements of this license.
341              
342             This license does not grant you the right to use any trademark, service
343             mark, tradename, or logo of the Copyright Holder.
344              
345             This license includes the non-exclusive, worldwide, free-of-charge
346             patent license to make, have made, use, offer to sell, sell, import and
347             otherwise transfer the Package with respect to any patent claims
348             licensable by the Copyright Holder that are necessarily infringed by the
349             Package. If you institute patent litigation (including a cross-claim or
350             counterclaim) against any party alleging that the Package constitutes
351             direct or contributory patent infringement, then this Artistic License
352             to you shall terminate on the date that such litigation is filed.
353              
354             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
355             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
356             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
357             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
358             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
359             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
360             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
361             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
362              
363              
364             =cut
365              
366             1; # End of HTML::FormHandlerX::JQueryRemoteValidator