File Coverage

blib/lib/HTML/FormHandlerX/Field/JSON.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::Field::JSON;
2             # ABSTRACT: a script tag which sets a var using JSON C<data>, encoded from perl data supplied via field for L<HTML::FormHandler>.
3             $HTML::FormHandlerX::Field::JSON::VERSION = '0.004';
4              
5 1     1   2074 use Moose;
  0            
  0            
6             extends 'HTML::FormHandler::Field::NoValue';
7             use namespace::autoclean;
8              
9             use JSON::MaybeXS;
10             use JavaScript::Minifier::XS qw();
11              
12             has 'data_key' => ( is => 'rw', isa => 'Str', builder => 'build_data_key', lazy => 1 );
13             sub build_data_key { HTML::FormHandler::Field::convert_full_name( shift->full_name ) }
14              
15             has 'data' => (
16             is => 'rw',
17             # isa => 'Str',
18             builder => 'build_data',
19             lazy => 1
20             );
21             sub build_data { '' }
22             has 'set_data' => ( isa => 'Str', is => 'ro' );
23              
24             has 'do_minify' => ( isa => 'Bool', is => 'rw', default => 0 );
25             has '+do_label' => ( default => 0 );
26              
27             has 'render_method' => (
28             traits => ['Code'],
29             is => 'ro',
30             isa => 'CodeRef',
31             lazy => 1,
32             predicate => 'does_render_method',
33             handles => { 'render' => 'execute_method' },
34             builder => 'build_render_method',
35             );
36              
37             sub build_render_method {
38             my $self = shift;
39              
40             my $set_data = $self->set_data;
41             $set_data ||= "data_" . HTML::FormHandler::Field::convert_full_name( $self->full_name );
42             return sub { my $self = shift; $self->wrap_data( $self->parent->$set_data($self) ); }
43             if ( $self->parent->has_flag('is_compound') && $self->parent->can($set_data) );
44             return sub { my $self = shift; $self->wrap_data( $self->form->$set_data($self) ); }
45             if ( $self->form && $self->form->can($set_data) );
46             return sub {
47             my $self = shift;
48             return $self->wrap_data( $self->data );
49             };
50             } ## end sub build_render_method
51              
52             sub _result_from_object {
53             my ( $self, $result, $value ) = @_;
54             $self->_set_result($result);
55             $self->value($value);
56             $result->_set_field_def($self);
57             return $result;
58             }
59              
60             sub wrap_data {
61             my $self = shift;
62              
63             my $json = $self->deflator( @_ > 1 ? [@_] : $_[0] );
64             chomp $json;
65              
66             my $data_key = $self->data_key;
67              
68             my $javascript = '';
69             if ( $data_key =~ m/.+\..+/ ) { # key contains 'dot' properties, so don't create a var, just set property
70             $javascript .= qq{\n $data_key = $json;};
71             } elsif ( $data_key =~ m/.+\.$/ ) { # key ends with 'dot', so assume data_key is object and field_name is property, don't create a var, just set property
72             my $property_key = HTML::FormHandler::Field::convert_full_name( $self->full_name );
73             $javascript .= qq{\n $data_key$property_key = $json;};
74             } elsif ( $data_key =~ m/^\..+/ )
75             { # key starts with 'dot', so assume data_key is property and field_name is object, don't create a var, just set property, and assume property is an array
76             my $object_key = HTML::FormHandler::Field::convert_full_name( $self->full_name );
77             $javascript .= qq{\n $object_key$data_key = $json;};
78             } else {
79             $javascript .= qq{\n var $data_key = $json;};
80             }
81              
82             my $output = qq{\n<script type="text/javascript">};
83             $output .= $self->do_minify ? JavaScript::Minifier::XS::minify($javascript) : $javascript;
84             $output .= qq{\n</script>};
85              
86             return $output;
87             } ## end sub wrap_data
88              
89              
90             has "json_opts" => (
91             is => "rw",
92             traits => ['Hash'],
93             isa => "HashRef",
94             default => sub { { pretty => undef, relaxed => undef, canonical => undef } },
95             handles => {
96             set_json_opt => 'set',
97             get_json_opt => 'get',
98             },
99             );
100              
101             sub deflator {
102             my ( $self, $value ) = @_;
103             my $pretty = $self->get_json_opt('pretty') // 1;
104             my $relaxed = $self->get_json_opt('relaxed') // 1;
105             my $canonical = $self->get_json_opt('canonical') // 1;
106             return JSON->new
107             ->utf8
108             ->allow_nonref
109             ->pretty($pretty)
110             ->relaxed($relaxed)
111             ->canonical($canonical)
112             ->encode($value)
113             || '';
114             } ## end sub deflator
115              
116              
117             __PACKAGE__->meta->make_immutable;
118             use namespace::autoclean;
119             1;
120              
121             __END__
122              
123             =pod
124              
125             =encoding UTF-8
126              
127             =head1 NAME
128              
129             HTML::FormHandlerX::Field::JSON - a script tag which sets a var using JSON C<data>, encoded from perl data supplied via field for L<HTML::FormHandler>.
130              
131             =head1 VERSION
132              
133             version 0.004
134              
135             =head1 SYNOPSIS
136              
137             This class can be used for fields that need to supply JSON data for use
138             by scripts in the form. It will JSONify and render the value returned by
139             a form's C<data_E<lt>field_nameE<gt>> method, or the field's C<data> attribute.
140              
141             has_field 'user_addresses' => ( type => 'JSON',
142             data => { john => 'john@example.com', sarah => 'sarah@example.com' } );
143              
144             or using a method:
145              
146             has_field 'user_addresses' => ( type => 'JSON' );
147             sub data_user_addresses {
148             my ( $self, $field ) = @_;
149             if( $field->value == 'foo' ) {
150             return { john => 'john@example.com', sarah => 'sarah@example.com' };
151             } else {
152             return [ 'john@example.com', 'sarah@example.com' ];
153             }
154             }
155             #----
156             has_field 'usernames' => ( type => 'JSON' );
157             sub data_usernames {
158             my ( $self, $field ) = @_;
159             return [ qw'john sarah' ];
160             }
161              
162             or set the name of the data generation method:
163              
164             has_field 'user_addresses' => ( type => 'JSON', set_data => 'my_user_addresses' );
165             sub my_user_addresses {
166             ....
167             }
168              
169             or provide a 'render_method':
170              
171             has_field 'user_addresses' => ( type => 'JSON', render_method => \&render_user_addresses );
172             sub render_user_addresses {
173             my $self = shift;
174             ....
175             return q(
176             <script type="text/javascript">
177             // JSON assignment here
178             var myVar = 'abc';
179             </script>);
180             }
181              
182             The data generation methods should return a scalar (hashref or
183             arrayref), which will be encoded as JSON, given a variable assignment,
184             and wrapped in script tags. If you supply your own 'render_method' then
185             you are responsible for calling C<$self-E<gt>deflator> or
186             C<$self-E<gt>wrap_data> yourself.
187              
188             =head1 FIELD OPTIONS
189              
190             We support the following additional field options, over what is
191             inherited from L<HTML::FormHandler::Field>
192              
193             =over
194              
195             =item data
196              
197             Scalar (hashref or arrayref) holding the data to be encoded as JSON.
198              
199             =item set_data
200              
201             Name of method that gets called to generate the data.
202              
203             =item data_key
204              
205             Name of JavaScript variable that will be assigned the JSON object. See
206             L</"JavaScript variable names">
207              
208             =item do_minify
209              
210             Boolean to indicate whether code should be minified using
211             L<JavaScript::Minifier::XS>
212              
213             =item json_opts
214              
215             Hashref with 3 possible keys; C<pretty>, C<relaxed>, C<canonical>. The
216             values are passed to L<JSON> when encoding the data.
217              
218             =back
219              
220             =head1 FIELD METHODS
221              
222             The following methods can be called on the field.
223              
224             =over
225              
226             =item deflator
227              
228             The C<deflator> method is called to encode the C<data> as JSON. The
229             C<json_opts> attribute is used to control options for JSON encode.
230              
231             =item wrap_data
232              
233             The C<wrap_data> method calls C<$self-E<gt>deflator>, sets the variable
234             assignment using the JSON object, minifies the code, and wraps the code
235             in script tags.
236              
237             =back
238              
239             =head1 JavaScript variable names
240              
241             By default, the name of the variable being assigned is same as the field
242             name. The variable name can be changed with the data_key attribute. If
243             the field name (or data_key value) is a simple string (no dot separator)
244             then the variable will be defined with C<var varName;>:
245              
246             has_field 'user_addresses' => ( type => 'JSON',
247             data => [ qw'john@acme.org sarah@acme.org' ],
248             );
249              
250             will render as:
251              
252             <script type="text/javascript">
253             var user_addresses = [ "john@acme.org", "sarah@acme.org" ];
254             </script>);
255              
256             Otherwise it is assumed the variable is already defined:
257              
258             has_field 'user_addresses' => ( type => 'JSON',
259             data_key => 'user_addresses.names',
260             data => [ qw'john sarah' ],
261             );
262              
263             will render as:
264              
265             <script type="text/javascript">
266             user_addresses.names = [ "john", "sarah" ];
267             </script>);
268              
269             The data_key can begin or end with a dot, in which case the field name
270             is either appended or prepended to the data_key.
271              
272             has_field 'user_addresses' => ( type => 'JSON',
273             data_key => '.email',
274             data => [ qw'john@acme.org sarah@acme.org' ],
275             );
276              
277             Will render as:
278              
279             <script type="text/javascript">
280             user_addresses.email = [ "john@acme.org", "sarah@acme.org" ];
281             </script>);
282              
283             =head1 AUTHOR
284              
285             Charlie Garrison <garrison@zeta.org.au>
286              
287             =head1 COPYRIGHT AND LICENSE
288              
289             This software is copyright (c) 2014 by Charlie Garrison.
290              
291             This is free software; you can redistribute it and/or modify it under
292             the same terms as the Perl 5 programming language system itself.
293              
294             =cut