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   52878 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.04
14              
15             =cut
16              
17             our $VERSION = '0.04';
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             });
138             }
139             });
140             });
141             SCRIPT
142              
143             return $script;
144             }
145              
146             has 'js_apply_error_classes' => (is => 'rw', isa => 'Str', default => <
147             function(element) {
148             \$(element).closest('.form-group').removeClass('success').addClass('error');
149             }
150             JS
151              
152             has 'js_apply_success_classes' => (is => 'rw', isa => 'Str', default => <
153             function(element) {
154             element
155             .text('dummy').addClass('valid')
156             .closest('.form-group').removeClass('error').addClass('success');
157             }
158             JS
159              
160             =head1 CONFIGURATION AND SETUP
161              
162             C adds jQuery scripts to your form to
163             gather form input and send it to the server for validation before the user submits the
164             completed form. The server responds with messages that are displayed on the form.
165             So you will need to set up various bits and pieces. Most have straightforward defaults.
166              
167             =head2 C
168              
169             Default: /ajax/formvalidator
170              
171             The form data will be POSTed to C<[validation_endpoint]/[form_name]/[field_name]>.
172              
173             Note that *all* fields are submitted, not just the field being validated.
174              
175             You must write the code to handle this submission. The response should be a JSON
176             string, either C if the field passed its tests, or a message describing
177             the error. The message will be displayed on the form.
178              
179             The synopsis has an example in Poet/Mason.
180              
181             =head2 C
182              
183             Default: http://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js
184              
185             You can leave this as-is, or if you prefer, you can put the file on your own
186             server and modify this setting to point to it.
187              
188             =head2 C
189              
190             Default:
191              
192             function(element) {
193             \$(element).closest('.form-group').removeClass('success').addClass('error');
194             }
195              
196             JavaScript function called when a field fails validation. The function is passed
197             the form-control element that failed.
198              
199             =head2 C
200              
201             Default:
202              
203             function(element) {
204             element
205             .text('dummy').addClass('valid')
206             .closest('.form-group').removeClass('error').addClass('success');
207             }
208              
209             JavaScript function called when a field passes validation. The function is passed
210             the form-control element that succeeded.
211              
212             =head2 C
213              
214             Default: C<[ qw(Hidden noCAPTCHA Display JSON JavaScript) ]>
215              
216             A list of field types that should not be included in the validation calls.
217              
218             =head2 C
219              
220             Default: C<[ qw(submit) ]>
221              
222             A list of field names that should not be included in the validation calls.
223              
224             =head2 C
225              
226             Boolean, default 0.
227              
228             A flag to turn off remote validation altogether, perhaps useful during form development.
229              
230              
231             =head2 jQuery
232              
233             You need to load the jQuery library yourself. See https://jquery.com/download/
234              
235             =head2 CSS
236              
237             You will probably want to style the C and C classes on your
238             form, for example:
239              
240             label.valid {
241             width: 24px;
242             height: 24px;
243             background: url(/static/images/valid.png) center center no-repeat;
244             display: inline-block;
245             text-indent: -9999px;
246             }
247              
248             label.error {
249             font-weight: normal;
250             color: red;
251             padding: 2px 8px;
252             margin-top: 2px;
253             }
254              
255             =cut
256              
257              
258              
259              
260             =head1 See also
261              
262             =over 4
263              
264             =item L
265              
266             =item L
267              
268             =item L
269              
270             =back
271              
272             =cut
273              
274             =head1 AUTHOR
275              
276             David R. Baird, C<< >>
277              
278             =head1 CODE REPOSITORY
279              
280             L
281              
282             Please report any bugs or feature requests there.
283              
284              
285             =head1 SUPPORT
286              
287             You can find documentation for this module with the perldoc command.
288              
289             perldoc HTML::FormHandlerX::JQueryRemoteValidator
290              
291              
292             You can also look for information at:
293              
294             =over 4
295              
296             =item * MetaCPAN
297              
298             L
299              
300              
301             =item * AnnoCPAN: Annotated CPAN documentation
302              
303             L
304              
305             =item * CPAN Ratings
306              
307             L
308              
309             =item * Search CPAN
310              
311             L
312              
313             =back
314              
315              
316             =head1 ACKNOWLEDGEMENTS
317              
318             This started out as a modification of Aaron Trevana's
319             HTML::FormHandlerX::Form::JQueryValidator
320              
321              
322             =head1 LICENSE AND COPYRIGHT
323              
324             Copyright 2016 David R. Baird.
325              
326             This program is free software; you can redistribute it and/or modify it
327             under the terms of the the Artistic License (2.0). You may obtain a
328             copy of the full license at:
329              
330             L
331              
332             Any use, modification, and distribution of the Standard or Modified
333             Versions is governed by this Artistic License. By using, modifying or
334             distributing the Package, you accept this license. Do not use, modify,
335             or distribute the Package, if you do not accept this license.
336              
337             If your Modified Version has been derived from a Modified Version made
338             by someone other than you, you are nevertheless required to ensure that
339             your Modified Version complies with the requirements of this license.
340              
341             This license does not grant you the right to use any trademark, service
342             mark, tradename, or logo of the Copyright Holder.
343              
344             This license includes the non-exclusive, worldwide, free-of-charge
345             patent license to make, have made, use, offer to sell, sell, import and
346             otherwise transfer the Package with respect to any patent claims
347             licensable by the Copyright Holder that are necessarily infringed by the
348             Package. If you institute patent litigation (including a cross-claim or
349             counterclaim) against any party alleging that the Package constitutes
350             direct or contributory patent infringement, then this Artistic License
351             to you shall terminate on the date that such litigation is filed.
352              
353             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
354             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
355             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
356             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
357             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
358             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
359             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
360             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
361              
362              
363             =cut
364              
365             1; # End of HTML::FormHandlerX::JQueryRemoteValidator