Group
Extension

HTML-FormHandlerX-Field-JavaScript/lib/HTML/FormHandlerX/Field/JSON.pm

package HTML::FormHandlerX::Field::JSON;
# ABSTRACT: a script tag which sets a var using JSON C<data>, encoded from perl data supplied via field for L<HTML::FormHandler>.
$HTML::FormHandlerX::Field::JSON::VERSION = '0.004';

use Moose;
extends 'HTML::FormHandler::Field::NoValue';
use namespace::autoclean;

use JSON::MaybeXS;
use JavaScript::Minifier::XS qw();

has 'data_key' => ( is => 'rw', isa => 'Str', builder => 'build_data_key', lazy => 1 );
sub build_data_key { HTML::FormHandler::Field::convert_full_name( shift->full_name ) }

has 'data' => (
	is => 'rw',
# 	isa     => 'Str',
	builder => 'build_data',
	lazy    => 1
);
sub build_data { '' }
has 'set_data' => ( isa => 'Str', is => 'ro' );

has 'do_minify' => ( isa => 'Bool', is => 'rw', default => 0 );
has '+do_label' => ( default => 0 );

has 'render_method' => (
	traits    => ['Code'],
	is        => 'ro',
	isa       => 'CodeRef',
	lazy      => 1,
	predicate => 'does_render_method',
	handles   => { 'render' => 'execute_method' },
	builder   => 'build_render_method',
);

sub build_render_method {
	my $self = shift;

	my $set_data = $self->set_data;
	$set_data ||= "data_" . HTML::FormHandler::Field::convert_full_name( $self->full_name );
	return sub { my $self = shift; $self->wrap_data( $self->parent->$set_data($self) ); }
	  if ( $self->parent->has_flag('is_compound') && $self->parent->can($set_data) );
	return sub { my $self = shift; $self->wrap_data( $self->form->$set_data($self) ); }
	  if ( $self->form && $self->form->can($set_data) );
	return sub {
		my $self = shift;
		return $self->wrap_data( $self->data );
	};
} ## end sub build_render_method

sub _result_from_object {
	my ( $self, $result, $value ) = @_;
	$self->_set_result($result);
	$self->value($value);
	$result->_set_field_def($self);
	return $result;
}

sub wrap_data {
	my $self = shift;

	my $json = $self->deflator( @_ > 1 ? [@_] : $_[0] );
	chomp $json;

	my $data_key = $self->data_key;

	my $javascript = '';
	if ( $data_key =~ m/.+\..+/ ) {    # key contains 'dot' properties, so don't create a var, just set property
		$javascript .= qq{\n  $data_key = $json;};
	} 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
		my $property_key = HTML::FormHandler::Field::convert_full_name( $self->full_name );
		$javascript .= qq{\n  $data_key$property_key = $json;};
	} elsif ( $data_key =~ m/^\..+/ )
	{   # 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
		my $object_key = HTML::FormHandler::Field::convert_full_name( $self->full_name );
		$javascript .= qq{\n  $object_key$data_key = $json;};
	} else {
		$javascript .= qq{\n  var $data_key = $json;};
	}

	my $output = qq{\n<script type="text/javascript">};
	$output .= $self->do_minify ? JavaScript::Minifier::XS::minify($javascript) : $javascript;
	$output .= qq{\n</script>};

	return $output;
} ## end sub wrap_data


has "json_opts" => (
	is      => "rw",
	traits  => ['Hash'],
	isa     => "HashRef",
	default => sub { { pretty => undef, relaxed => undef, canonical => undef } },
	handles => {
		set_json_opt => 'set',
		get_json_opt => 'get',
	},
);

sub deflator {
	my ( $self, $value ) = @_;
	my $pretty    = $self->get_json_opt('pretty')    // 1;
	my $relaxed   = $self->get_json_opt('relaxed')   // 1;
	my $canonical = $self->get_json_opt('canonical') // 1;
	return JSON->new
					->utf8
					->allow_nonref
					->pretty($pretty)
					->relaxed($relaxed)
					->canonical($canonical)
					->encode($value)
			 || '';
} ## end sub deflator


__PACKAGE__->meta->make_immutable;
use namespace::autoclean;
1;

__END__

=pod

=encoding UTF-8

=head1 NAME

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>.

=head1 VERSION

version 0.004

=head1 SYNOPSIS

This class can be used for fields that need to supply JSON data for use
by scripts in the form. It will JSONify and render the value returned by
a form's C<data_E<lt>field_nameE<gt>> method, or the field's C<data> attribute.

  has_field 'user_addresses' => ( type => 'JSON',
     data => { john => 'john@example.com', sarah => 'sarah@example.com' } );

or using a method:

  has_field 'user_addresses' => ( type => 'JSON' );
  sub data_user_addresses {
     my ( $self, $field ) = @_;
     if( $field->value == 'foo' ) {
        return { john => 'john@example.com', sarah => 'sarah@example.com' };
     } else {
        return [ 'john@example.com', 'sarah@example.com' ];
     }
  }
  #----
  has_field 'usernames' => ( type => 'JSON' );
  sub data_usernames {
      my ( $self, $field ) = @_;
      return [ qw'john sarah' ];
  }

or set the name of the data generation method:

   has_field 'user_addresses' => ( type => 'JSON', set_data => 'my_user_addresses' );
   sub my_user_addresses {
     ....
   }

or provide a 'render_method':

   has_field 'user_addresses' => ( type => 'JSON', render_method => \&render_user_addresses );
   sub render_user_addresses {
       my $self = shift;
       ....
       return q(
   <script type="text/javascript">
     // JSON assignment here
     var myVar = 'abc';
   </script>);
   }

The data generation methods should return a scalar (hashref or
arrayref), which will be encoded as JSON, given a variable assignment,
and wrapped in script tags. If you supply your own 'render_method' then
you are responsible for calling C<$self-E<gt>deflator> or
C<$self-E<gt>wrap_data> yourself.

=head1 FIELD OPTIONS

We support the following additional field options, over what is
inherited from L<HTML::FormHandler::Field>

=over

=item data

Scalar (hashref or arrayref) holding the data to be encoded as JSON.

=item set_data

Name of method that gets called to generate the data.

=item data_key

Name of JavaScript variable that will be assigned the JSON object. See
L</"JavaScript variable names">

=item do_minify

Boolean to indicate whether code should be minified using
L<JavaScript::Minifier::XS>

=item json_opts

Hashref with 3 possible keys; C<pretty>, C<relaxed>, C<canonical>. The
values are passed to L<JSON> when encoding the data.

=back

=head1 FIELD METHODS

The following methods can be called on the field.

=over

=item deflator

The C<deflator> method is called to encode the C<data> as JSON. The
C<json_opts> attribute is used to control options for JSON encode.

=item wrap_data

The C<wrap_data> method calls C<$self-E<gt>deflator>, sets the variable
assignment using the JSON object, minifies the code, and wraps the code
in script tags.

=back

=head1 JavaScript variable names

By default, the name of the variable being assigned is same as the field
name. The variable name can be changed with the data_key attribute. If
the field name (or data_key value) is a simple string (no dot separator)
then the variable will be defined with C<var varName;>:

  has_field 'user_addresses' => ( type => 'JSON',
  	data => [ qw'john@acme.org sarah@acme.org' ],
   );

will render as:

  <script type="text/javascript">
	var user_addresses = [ "john@acme.org", "sarah@acme.org" ];
  </script>);

Otherwise it is assumed the variable is already defined:

  has_field 'user_addresses' => ( type => 'JSON',
  	data_key => 'user_addresses.names',
  	data => [ qw'john sarah' ],
   );

will render as:

  <script type="text/javascript">
	user_addresses.names = [ "john", "sarah" ];
  </script>);

The data_key can begin or end with a dot, in which case the field name
is either appended or prepended to the data_key.

  has_field 'user_addresses' => ( type => 'JSON',
  	data_key => '.email',
  	data => [ qw'john@acme.org sarah@acme.org' ],
   );

Will render as:

  <script type="text/javascript">
	user_addresses.email = [ "john@acme.org", "sarah@acme.org" ];
  </script>);

=head1 AUTHOR

Charlie Garrison <garrison@zeta.org.au>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2014 by Charlie Garrison.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.