The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Data::JavaScript::LiteObject;
use strict;
use vars qw($VERSION $JSVER);
$VERSION = '1.04';

sub import{
  no strict 'refs';
  shift;
  $JSVER = shift || 1.0;
  *{"@{[scalar caller()]}::jsodump"} = \&jsodump;
}

sub jsodump {
  my %opts = @_;
  my(@keys, $obj, @objs, $EOL, $EOI, @F);

  unless( $opts{protoName} && $opts{dataRef} ){
    return warn("// Both protoName and dataRef must be supplied");
  }

  ($EOI, $EOL) = $opts{explode} ? ("$/\t")x2 : ('', ' ');

  if( ref($opts{dataRef}) eq "ARRAY" ){
    my $i=0;
    $opts{dataRef} =  {map {$opts{protoName}.$i++=>$_} @{$opts{dataRef}} };
  }
  #NOT elsif
  if( ref($opts{dataRef}) eq "HASH" ){
    if( ref($opts{attributes}) eq "ARRAY" ){
      @keys = @{$opts{attributes}};
    }
    else{
      @keys = sort { $a cmp $b } keys
	%{$opts{dataRef}->{(sort keys %{$opts{dataRef}})[0]}};
    }
  }
  else{
    warn("// Unknown reference type, attributes"); return;
  }

  push @F, "function $opts{protoName} (", join(', ', @keys) ,") {$/\t";
  push @F, map("this.$_ = $_;$EOL", @keys);  
  push @F, "}$/";

  foreach $obj ( sort keys %{$opts{dataRef}} ){
    push @F, "$obj = new $opts{protoName}($EOI";
    push @F, join(",$EOL",
		  map(datum($opts{dataRef}->{$obj}->{$_}), @keys) ).$EOL;
    push @F, ");$/";
    push @objs, $obj;
  }

  if( defined($opts{listObjects}) ){
    push @F, "$opts{listObjects} = new Array($EOI",
      join(",$EOL", map("'$_'", @objs)), ");$/";
  }

  if( defined($opts{lineIN}) ){
    local $. = $opts{lineIN}+1;
    @F = split($/, join('', @F));
    foreach ( @F ) {
      $_ .= $/ . '// '. ++$. unless (++$.-$opts{lineIN}) %5;
      $_ .= $/;
    }
    ${$opts{lineOUT}} = $.;
    unshift @F, '// '. ($opts{lineIN}+1) .$/;
  }
  return @F;
}

sub datum {
  local $_ = shift() || '';
  my $val;

  if ( ref eq "ARRAY" ) {
    $val = $JSVER >= 1.2 ?
      "[" . join(',',
		 map /^-?(?:\d+(?:\.\d*)?|\.\d+)$/ ?
		 $_ : do{ s/'/\\'/g; qq('$_') }, @{$_})
	. "]"
    :

      "new Array(" . join(',',
			  map /^-?(?:\d+(?:\.\d*)?|\.\d+)$/ ?
			  $_ : do{ s/'/\\'/g; qq('$_') }, @{$_})
	. ")";

  }
  elsif( $val = $_, $val !~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/ ){
    s/'/\\'/g;
    $val = qq('$_');
  }

  return $val;
}

1;
__END__

=pod

=head1 NAME

Data::JavaScript::LiteObject - lightweight data dumping to JavaScript

=head1 SYNOPSIS

    use Data::JavaScript:LiteObject;
    #OR
    use Data::JavaScript:LiteObject '1.2';

    %A = (protein      => 'bacon',
          condiments   => 'mayonaise',
          produce      => [qw(lettuce tomato)]);
    %B = (protein      => 'peanut butter',
          condiments   => 'jelly');
    @lunch             = (\%A, \%B);
    %lunch             = (BLT=>\%A, PBnJ=>\%B);

    jsodump(protoName  => "sandwich",
            dataRef    => \%lunch
            attributes => [qw(condiments protein produce)]);

=head1 DESCRIPTION

This module was inspired by L<Data::JavaScript>, which while incredibly
versatile, seems rather brute force and inelegant for certain forms
of data. Specifically a series of objects of the same class, which it
seems is a likely use for this kind of feature. So this module was
created to provide a lightweight means of producing configurable, clean
and compact output.

B<LiteObject> is used to format and output loh, hoh, lohol, and hohol.
The output is JavaScript 1.0 compatible, with the limitation that none
of the properties be a single-element array whose value is a number.
To lift this limitation pass use the extra value I<'1.2'>, which will
generate JavaScript 1.2 compatible output.

One function, B<jsodump>, is exported. B<jsodump> accepts a list of named
parameters; two of these are required and the rest are optional.

=head2 Required parameters

=over 4

=item C<protoName>

The name to be used for the prototype object function.

=item C<dataRef>

A reference to an array of hashes(loh) or hash of hashes(hoh) to dump.

=back

=head2 Optional parameters

=over 4

=item C<attributes>

A reference to an array containing a list of the object attributes
(hash keys). This is useful if every object is not guaranteed to
posses a value for each attribute.
It could also be used to exclude data from being dumped.

=item C<explode>

A scalar, if true output is one I<attribute> per line.
The default; false; is one I<object> per line.

=item C<lineIN>

A scalar, if true output is numbered every 5 lines. The value provided
should be the number of lines printed before this output.
For example if a CGI script included:

    print q(<html>
	    <head>
	    <title>Pthbb!!</title>
	    <script language=javascript>);>
    jsodump(protoName  => "sandwich",
            dataRef    => \@lunch,
            lineIN     => 4);

The client would see:

    <html>
    <head>
    <title>Pthbb!!</title>
    <script language=javascript>
    // 5
    function sandwich (condiment, produce, protein) {
            this.condiment = condiment; this.produce = produce; this.protein = protein; }
    BLT = new sandwich('mayonaise', new Array('lettuce','tomato'), 'bacon' );
    PBnJ = new sandwich('jelly', '', 'peanut butter' );
    // 10

making it easier to read and/or debug.

=item C<lineOUT>

A reference to a scalar. B<jsodump> will set the scalar's value to the number
of the last line of numbered output produced when lineIN is specified. Thus
you may pass the scalar to a subsequent call to B<jsodump> as the value of
lineIn for continuous numbering.
For example:

    jsodump(protoName  => "sandwich",
              dataRef  => \@lunch,
              lineIN   => 4,
              lineOUT  => \$.);
    jsodump(protoName  => "sandwich",
              dataRef  => \%lunch,
              lineIN   => $.);

=item C<listObjects>

A scalar, if true the parameters value is used as the name of an array to
be output which will contain a list of all the dumped object.
This allows data-ignorant client side code which need only
traverse the named array.

    jsodump(protoName  => "sandwich",
            dataRef    => \@lunch,
            listObjects=> "sandwiches");

would append the following to the output

    sandwiches = new Array('BLT', 'PBnJ');

=back

=head1 BUGS

Nothing that am I aware of.

=head1 SEE ALSO

L<Data::JavaScript>, L<Data::Dumper>

=head1 AUTHOR

Jerrad Pierce I<jpierce@cpan.org>, I<webmaster@pthbb.org>.
F<http://pthbb.org/>

=cut