File Coverage

blib/lib/Data/Google/Visualization/DataTable.pm
Criterion Covered Total %
statement 207 226 91.5
branch 77 108 71.3
condition 19 31 61.2
subroutine 15 16 93.7
pod 8 8 100.0
total 326 389 83.8


line stmt bran cond sub pod time code
1             package Data::Google::Visualization::DataTable;
2             $Data::Google::Visualization::DataTable::VERSION = '0.11';
3 5     5   9596 use strict;
  5         5  
  5         135  
4 5     5   17 use warnings;
  5         5  
  5         110  
5              
6 5     5   16 use Carp qw(croak carp);
  5         6  
  5         285  
7 5     5   2992 use Storable qw(dclone);
  5         12894  
  5         296  
8 5     5   2707 use Time::Local;
  5         6930  
  5         11396  
9              
10             =head1 NAME
11              
12             Data::Google::Visualization::DataTable - Easily create Google DataTable objects
13              
14             =head1 VERSION
15              
16             version 0.11
17              
18             =head1 DESCRIPTION
19              
20             Easily create Google DataTable objects without worrying too much about typed
21             data
22              
23             =head1 OVERVIEW
24              
25             Google's excellent Visualization suite requires you to format your Javascript
26             data very carefully. It's entirely possible to do this by hand, especially with
27             the help of the most excellent L but it's a bit fiddly, largely
28             because Perl doesn't natively support data types and Google's API accepts a
29             super-set of JSON - see L below.
30              
31             This module is attempts to hide the gory details of preparing your data before
32             sending it to a JSON serializer - more specifically, hiding some of the hoops
33             that have to be jump through for making sure your data serializes to the right
34             data types.
35              
36             More about the
37             L.
38              
39             Every effort has been made to keep naming conventions as close as possible to
40             those in the API itself.
41              
42             B
43             familiar with L and L.>
44              
45             =head1 SYNOPSIS
46              
47             use Data::Google::Visualization::DataTable;
48              
49             my $datatable = Data::Google::Visualization::DataTable->new();
50              
51             $datatable->add_columns(
52             { id => 'date', label => "A Date", type => 'date', p => {}},
53             { id => 'datetime', label => "A Datetime", type => 'datetime' },
54             { id => 'timeofday',label => "A Time of Day", type => 'timeofday' },
55             { id => 'bool', label => "True or False", type => 'boolean' },
56             { id => 'number', label => "Number", type => 'number' },
57             { id => 'string', label => "Some String", type => 'string' },
58             );
59              
60             $datatable->add_rows(
61              
62             # Add as array-refs
63             [
64             { v => DateTime->new() },
65             { v => Time::Piece->new(), f => "Right now!" },
66             { v => [6, 12, 1], f => '06:12:01' },
67             { v => 1, f => 'YES' },
68             15.6, # If you're getting lazy
69             { v => 'foobar', f => 'Foo Bar', p => { display => 'none' } },
70             ],
71              
72             # And/or as hash-refs (but only if you defined id's for each of your columns)
73             {
74             date => DateTime->new(),
75             datetime => { v => Time::Piece->new(), f => "Right now!" },
76             timeofday => [6, 12, 1],
77             bool => 1,
78             number => 15.6,
79             string => { v => 'foobar', f => 'Foo Bar' },
80             },
81              
82             );
83              
84             # Get the data...
85              
86             # Fancy-pants
87             my $output = $datatable->output_javascript(
88             columns => ['date','number','string' ],
89             pretty => 1,
90             );
91              
92             # Vanilla
93             my $output = $datatable->output_javascript();
94              
95             =head1 COLUMNS, ROWS AND CELLS
96              
97             We've tried as far as possible to stay as close as possible to the underlying
98             API, so make sure you've had a good read of:
99             L.
100              
101             =head2 Columns
102              
103             I are specified using a hashref, and follow exactly the format of the
104             underlying API itself. All of C, C, C
105             supported. The contents of C

will be passed directly to L to

106             serialize as a whole.
107              
108             =head2 Rows
109              
110             A row is either a hash-ref where the keys are column IDs and the values are
111             I, or an array-ref where the values are I.
112              
113             =head2 Cells
114              
115             I can be specified in several ways, but the best way is using a hash-ref
116             that exactly conforms to the API. C is NOT checked against your data type -
117             but we will attempt to convert it. If you pass in an undefined value, it will
118             return a JS 'null', regardless of the data type. C needs to be a string if
119             you provide it. C

will be bassed directly to L.

120              
121             For any of the date-like fields (C, C, C), you can
122             pass in 4 types of values. We accept L objects, L
123             objects, epoch seconds (as a string - converted internally using
124             L), or an array-ref of values that will be passed
125             directly to the resulting Javascript Date object eg:
126              
127             Perl:
128             date => [ 5, 4, 3 ]
129             JS:
130             new Date( 5, 4, 3 )
131              
132             Remember that JS dates 0-index the month. B
133             Dates and Times below if you want any chance of doing this right>...
134              
135             For non-date fields, if you specify a cell using a string or number, rather than
136             a hashref, that'll be mapped to a cell with C set to the string you
137             specified.
138              
139             C: we test the value you pass in for truth, the Perl way, although
140             undef values will come out as null, not 0.
141              
142             =head2 Properties
143              
144             Properties can be defined for the whole datatable (using C), for
145             each column (using C

), for each row (using C

) and for each cell (again

146             using C

). The documentation provided is a little unclear as to exactly

147             what you're allowed to put in this, so we provide you ample rope and let you
148             specify anything you like.
149              
150             When defining properties for rows, you must use the hashref method of row
151             creation. If you have a column with id of C

, you must use C<_p> as your key

152             for defining properties.
153              
154             =head1 METHODS
155              
156             =head2 new
157              
158             Constructor. B
159             all to the constructor>. Accepts a hashref of arguments:
160              
161             C

- a datatable-wide properties element (see C above and the

162             Google docs).
163              
164             C - defaults to false. An experimental feature for doing dates
165             the right way. See: L for discussion below.
166              
167             C - optional, and defaults to a sensibly configured L
168             object. If you really want to avoid using L for some reason, you can
169             pass in something else here that supports an C method (and also avoid
170             loading L at all, as we lazy-load it). If you just want to configure
171             the L object we use, consider using the C method
172             specified below instead. B.
173              
174             =cut
175              
176             sub new {
177 8     8 1 14606 my $class = shift;
178 8   100     41 my $args = shift || {};
179 8   100     94 my $self = {
180             columns => [],
181             column_mapping => {},
182             rows => [],
183             all_columns_have_ids => 0,
184             column_count => 0,
185             pedantic => 1,
186             with_timezone => ($args->{'with_timezone'} || 0)
187             };
188 8         22 bless $self, $class;
189              
190 8 100       34 $self->{'properties'} = $args->{'p'} if defined $args->{'p'};
191 8   33     40 $self->{'json_xs'} = $args->{'json_object'} ||
192             $self->_create_json_xs_object();
193              
194 8         25 return $self;
195             }
196              
197             # We don't actually need JSON::XS, and in fact, there's a user who'd rather we
198             # didn't insist on it, so we lazy load both the class and our object
199             sub _create_json_xs_object {
200 8     8   9 my $self = shift;
201 8         2284 require JSON::XS;
202 8         12173 return JSON::XS->new()->canonical(1)->allow_nonref;
203             }
204              
205             =head2 add_columns
206              
207             Accepts zero or more columns, in the format specified above, and adds them to
208             our list of columns. Returns the object. You can't call this method after you've
209             called C for the first time.
210              
211             =cut
212              
213             our %ACCEPTABLE_TYPES = map { $_ => 1 } qw(
214             date datetime timeofday boolean number string
215             );
216              
217             our %JAVASCRIPT_RESERVED = map { $_ => 1 } qw(
218             break case catch continue default delete do else finally for function if in
219             instanceof new return switch this throw try typeof var void while with
220             abstract boolean byte char class const debugger double enum export extends
221             final float goto implements import int interface long native package
222             private protected public short static super synchronized throws transient
223             volatile const export import
224             );
225              
226             sub add_columns {
227 8     8 1 83 my ($self, @columns) = @_;
228              
229 8         32 croak "You can't add columns once you've added rows"
230 8 50       10 if @{$self->{'rows'}};
231              
232             # Add the columns to our internal store
233 8         19 for my $column ( @columns ) {
234              
235             # Check the type
236 21         26 my $type = $column->{'type'};
237 21 50       37 croak "Every column must have a 'type'" unless $type;
238 21 50       47 croak "Unknown column type '$type'" unless $ACCEPTABLE_TYPES{ $type };
239              
240             # Check label and ID are sane
241 21         32 for my $key (qw( label id pattern ) ) {
242 63 50 66     180 if ( $column->{$key} && ref( $column->{$key} ) ) {
243 0         0 croak "'$key' needs to be a simple string";
244             }
245             }
246              
247             # Check the 'p' column is ok if it was provided, and convert now to JSON
248 21 100       54 if ( defined($column->{'p'}) ) {
249 3         5 eval { $self->json_xs_object->encode( $column->{'p'} ) };
  3         12  
250 3 50       9 croak "Serializing 'p' failed: $@" if $@;
251             }
252              
253             # ID must be unique
254 21 100       43 if ( $column->{'id'} ) {
255 19         18 my $id = $column->{'id'};
256 19 50       20 if ( grep { $id eq $_->{'id'} } @{ $self->{'columns'} } ) {
  18         39  
  19         34  
257 0         0 croak "We already have a column with the id '$id'";
258             }
259             }
260              
261             # Pedantic checking of that ID
262 21 50       41 if ( $self->pedantic ) {
263 21 100       35 if ( $column->{'id'} ) {
264 19 50       104 if ( $column->{'id'} !~ m/^[a-zA-Z0-9_]+$/ ) {
    50          
265 0         0 carp "The API recommends that t ID's should be both simple:"
266             . $column->{'id'};
267             } elsif ( $JAVASCRIPT_RESERVED{ $column->{'id'} } ) {
268 0         0 carp "The API recommends avoiding Javascript reserved " .
269             "words for IDs: " . $column->{'id'};
270             }
271             }
272             }
273              
274             # Add that column to our collection
275 21         18 push( @{ $self->{'columns'} }, $column );
  21         55  
276             }
277              
278             # Reset column statistics
279 8         18 $self->{'column_mapping'} = {};
280 8         16 $self->{'column_count' } = 0;
281 8         9 $self->{'all_columns_have_ids'} = 1;
282              
283             # Map the IDs to column indexes, redo column stats, and encode the column
284             # data
285 8         10 my $i = 0;
286 8         26 for my $column ( @{ $self->{'columns'} } ) {
  8         18  
287              
288 21         18 $self->{'column_count'}++;
289              
290             # Encode as JSON
291 21         24 delete $column->{'json'};
292 21         37 my $column_json = $self->json_xs_object->encode( $column );
293 21         28 $column->{'json'} = $column_json;
294              
295             # Column mapping
296 21 100       45 if ( $column->{'id'} ) {
297 19         33 $self->{'column_mapping'}->{ $column->{'id'} } = $i;
298             } else {
299 2         3 $self->{'all_columns_have_ids'} = 0;
300             }
301 21         25 $i++;
302             }
303              
304 8         20 return $self;
305             }
306              
307             =head2 add_rows
308              
309             Accepts zero or more rows, either as a list of hash-refs or a list of
310             array-refs. If you've provided hash-refs, we'll map the key name to the column
311             via its ID (you must have given every column an ID if you want to do this, or
312             it'll cause a fatal error).
313              
314             If you've provided array-refs, we'll assume each cell belongs in subsequent
315             columns - your array-ref must have the same number of members as you have set
316             columns.
317              
318             =cut
319              
320             sub add_rows {
321 10     10 1 175 my ( $self, @rows_to_add ) = @_;
322              
323             # Loop over our input rows
324 10         18 for my $row (@rows_to_add) {
325              
326 13         12 my @columns;
327             my $properties;
328              
329             # Map hash-refs to columns
330 13 100       33 if ( ref( $row ) eq 'HASH' ) {
    50          
331              
332             # Grab the properties, if they exist
333 8 50       20 if ( exists $self->{'column_mapping'}->{'p'} ) {
334 0         0 $properties = delete $row->{'_p'};
335             } else {
336 8         13 $properties = delete $row->{'p'};
337             }
338              
339             # We can't be going forward unless they specified IDs for each of
340             # their columns
341 8 50       23 croak "All your columns must have IDs if you want to add hashrefs" .
342             " as rows" unless $self->{'all_columns_have_ids'};
343              
344             # Loop through the keys, populating @columns
345 8         18 for my $key ( keys %$row ) {
346             # Get the relevant column index for the key, or handle 'p'
347             # properly
348 20 50       37 unless ( exists $self->{'column_mapping'}->{ $key } ) {
349 0         0 croak "Couldn't find a column with id '$key'";
350             }
351 20         23 my $index = $self->{'column_mapping'}->{ $key };
352              
353             # Populate @columns with the data-type and value
354 20         51 $columns[ $index ] = [
355             $self->{'columns'}->[ $index ]->{'type'},
356             $row->{ $key }
357             ];
358              
359             }
360              
361             # Map array-refs to columns
362             } elsif ( ref( $row ) eq 'ARRAY' ) {
363              
364             # Populate @columns with the data-type and value
365 5         7 my $i = 0;
366 5         6 for my $col (@$row) {
367 12         22 $columns[ $i ] = [
368             $self->{'columns'}->[ $i ]->{'type'},
369             $col
370             ];
371 12         12 $i++;
372             }
373              
374             # Rows must be array-refs or hash-refs
375             } else {
376 0         0 croak "Rows must be array-refs or hash-refs: $row";
377             }
378              
379             # Force the length of columns to be the same as actual columns, to
380             # handle undef values better.
381 13 100       37 $columns[ $self->{'column_count'} - 1 ] = undef
382             unless defined $columns[ $self->{'column_count'} - 1 ];
383              
384             # Convert each cell in to the long cell format
385 13         13 my @formatted_columns;
386 13         20 for ( @columns ) {
387 35 100       49 if ( $_ ) {
388 32         33 my ($type, $column) = @$_;
389              
390 32 100       44 if ( ref( $column ) eq 'HASH' ) {
391             # Check f is a simple string if defined
392 7 50 66     31 if ( defined($column->{'f'}) && ref( $column->{'f'} ) ) {
393 0         0 croak "Cell's 'f' values must be strings: " .
394             $column->{'f'};
395             }
396             # If p is defined, check it serializes
397 7 100       17 if ( defined($column->{'p'}) ) {
398 1 50       3 croak "'p' must be a reference"
399             unless ref( $column->{'p'} );
400 1         2 eval { $self->json_xs_object->encode( $column->{'p'} ) };
  1         2  
401 1 50       3 croak "Serializing 'p' failed: $@" if $@;
402             }
403             # Complain about any unauthorized keys
404 7 50       13 if ( $self->pedantic ) {
405 7         15 for my $key ( keys %$column ) {
406 14 50       47 carp "'$key' is not a recognized key"
407             unless $key =~ m/^[fvp]$/;
408             }
409             }
410 7         17 push( @formatted_columns, [ $type, $column ] );
411             } else {
412 25         57 push( @formatted_columns, [ $type, { v => $column } ] );
413             }
414             # Undefined that become nulls
415             } else {
416 3         5 push( @formatted_columns, [ 'null', { v => undef } ] );
417             }
418             }
419              
420             # Serialize each cell
421 13         15 my @cells;
422 13         15 for (@formatted_columns) {
423 35         42 my ($type, $cell) = @$_;
424              
425             # Force 'f' to be a string
426 35 100       62 if ( defined( $cell->{'f'} ) ) {
427 6         9 $cell->{'f'} .= '';
428             }
429              
430             # Handle null/undef
431 35 100       123 if ( ! defined($cell->{'v'}) ) {
    100          
    100          
    100          
432 10         13 push(@cells, $self->json_xs_object->encode( $cell ) );
433              
434             # Convert boolean
435             } elsif ( $type eq 'boolean' ) {
436 3 50       6 $cell->{'v'} = $cell->{'v'} ? \1 : \0;
437 3         14 push(@cells, $self->json_xs_object->encode( $cell ) );
438              
439             # Convert number
440             } elsif ( $type eq 'number' ) {
441 6 100       13 $cell->{'v'} = 0 unless $cell->{'v'}; # Force false values to 0
442 6         8 $cell->{'v'} += 0; # Force numeric for JSON encoding
443 6         14 push(@cells, $self->json_xs_object->encode( $cell ) );
444              
445             # Convert string
446             } elsif ( $type eq 'string' ) {
447 4         6 $cell->{'v'} .= '';
448 4         5 push(@cells, $self->json_xs_object->encode( $cell ) );
449              
450             # It's a date!
451             } else {
452 12         10 my @date_digits;
453              
454             # Date digits specified manually
455 12 50       21 if ( ref( $cell->{'v'} ) eq 'ARRAY' ) {
456 0         0 @date_digits = @{ $cell->{'v'} };
  0         0  
457             # We're going to have to retrieve them ourselves
458             } else {
459 12         7 my @initial_date_digits;
460             my $has_milliseconds;
461              
462             # Epoch timestamp
463 12 100       46 if (! ref( $cell->{'v'} ) ) {
    50          
    50          
464 6         99 my ($sec,$min,$hour,$mday,$mon,$year) =
465             localtime( $cell->{'v'} );
466 6         11 $year += 1900;
467 6         13 @initial_date_digits =
468             ( $year, $mon, $mday, $hour, $min, $sec );
469              
470             } elsif ( $cell->{'v'}->isa('DateTime') ) {
471 0         0 my $dt = $cell->{'v'};
472 0         0 @initial_date_digits = (
473             $dt->year, ( $dt->mon - 1 ), $dt->day,
474             $dt->hour, $dt->min, $dt->sec,
475             );
476 0 0       0 if ( $dt->millisecond ) {
477 0         0 $has_milliseconds++;
478 0         0 push( @initial_date_digits, $dt->millisecond );
479             }
480              
481             } elsif ( $cell->{'v'}->isa('Time::Piece') ) {
482 6         5 my $tp = $cell->{'v'};
483 6         12 @initial_date_digits = (
484             $tp->year, $tp->_mon, $tp->mday,
485             $tp->hour, $tp->min, $tp->sec,
486             );
487              
488             } else {
489 0         0 croak "Unknown date format";
490             }
491              
492 12 100       142 if ( $type eq 'date' ) {
    100          
493 4         12 @date_digits = @initial_date_digits[ 0 .. 2 ];
494             } elsif ( $type eq 'datetime' ) {
495 4         11 @date_digits = @initial_date_digits[ 0 .. 5 ];
496 4 50       16 push( @date_digits, $initial_date_digits[6] )
497             if $has_milliseconds;
498             } else { # Time of day
499 4         10 @date_digits = @initial_date_digits[ 3 .. 5 ];
500 4 50       9 push( @date_digits, $initial_date_digits[6] )
501             if $has_milliseconds;
502             }
503             }
504              
505 12         36 my $json_date = join ', ', @date_digits;
506 12 100       16 if ( $type eq 'timeofday' ) {
507 4         8 $json_date = '[' . $json_date . ']';
508             } else {
509 8         13 $json_date = 'new Date( ' . $json_date . ' )';
510             }
511              
512             # Actually, having done all this, timezone hack date...
513 12 0 100     61 if (
      66        
      66        
      0        
      33        
514             $self->{'with_timezone'} &&
515             ref ( $cell->{'v'} ) &&
516             ref ( $cell->{'v'} ) ne 'ARRAY' &&
517             $cell->{'v'}->isa('DateTime') &&
518             ( $type eq 'date' || $type eq 'datetime' )
519             ) {
520 0         0 $json_date = 'new Date("' .
521             $cell->{'v'}->strftime('%a, %d %b %Y %H:%M:%S GMT%z') .
522             '")';
523             }
524              
525 12         12 my $placeholder = '%%%PLEHLDER%%%';
526 12         17 $cell->{'v'} = $placeholder;
527 12         18 my $json_string = $self->json_xs_object->encode( $cell );
528 12         78 $json_string =~ s/"$placeholder"/$json_date/;
529 12         40 push(@cells, $json_string );
530             }
531             }
532              
533 13         207 my %data = ( cells => \@cells );
534 13 100       32 $data{'properties'} = $properties if defined $properties;
535              
536 13         14 push( @{ $self->{'rows'} }, \%data );
  13         55  
537             }
538              
539 10         20 return $self;
540             }
541              
542             =head2 pedantic
543              
544             We do some data checking for sanity, and we'll issue warnings about things the
545             API considers bad data practice - using reserved words or fancy characters and
546             IDs so far. If you don't want that, simple say:
547              
548             $object->pedantic(0);
549              
550             Defaults to true.
551              
552             =cut
553              
554             sub pedantic {
555 28     28 1 30 my ($self, $arg) = @_;
556 28 50       42 $self->{'pedantic'} = $arg if defined $arg;
557 28         62 return $self->{'pedantic'};
558             }
559              
560             =head2 set_properties
561              
562             Sets the datatable-wide properties value. See the Google docs.
563              
564             =cut
565              
566             sub set_properties {
567 2     2 1 3 my ( $self, $arg ) = @_;
568 2         4 $self->{'properties'} = $arg;
569 2         4 return $self->{'properties'};
570             }
571              
572             =head2 json_xs_object
573              
574             You may want to configure your L object in some magical way. This is
575             a read/write accessor to it. If you didn't understand that, or why you'd want
576             to do that, you can ignore this method.
577              
578             =cut
579              
580             sub json_xs_object {
581 65     65 1 56 my ($self, $arg) = @_;
582 65 50       97 $self->{'json_xs'} = $arg if defined $arg;
583 65         336 return $self->{'json_xs'};
584             }
585              
586             =head2 output_javascript
587              
588             Returns a Javascript serialization of your object. You can optionally specify two
589             parameters:
590              
591             C - I - defaults to false - that specifies if you'd like your Javascript
592             spread-apart with whitespace. Useful for debugging.
593              
594             C - I - pick out certain columns only (and in the
595             order you specify). If you don't provide an argument here, we'll use them all
596             and in the order set in C.
597              
598             =head2 output_json
599              
600             An alias to C above, with a very misleading name, as it outputs
601             Javascript, not JSON - see L below.
602              
603             =cut
604              
605 0     0 1 0 sub output_json { my ( $self, %params ) = @_; $self->output_javascript( %params ) }
  0         0  
606              
607             sub output_javascript {
608 22     22 1 10046 my ($self, %params) = @_;
609              
610 22         55 my ($columns, $rows) = $self->_select_data( %params );
611              
612 22         35 my ($t, $s, $n) = ('','','');
613 22 100       44 if ( $params{'pretty'} ) {
614 6         7 $t = " ";
615 6         6 $s = " ";
616 6         8 $n = "\n";
617             }
618              
619             # Columns
620 22         56 my $columns_string = join ',' .$n.$t.$t, @$columns;
621              
622             # Rows
623 31         46 my @rows = map {
624 22         29 my $tt = $t x 3;
625             # Turn the cells in to constituent values
626 31         38 my $individual_row_string = join ',' .$n.$tt.$t, @{$_->{'cells'}};
  31         48  
627             # Put together the output itself
628 31         81 my $output =
629             '{' .$n.
630             $tt. '"c":[' .$n.
631             $tt.$t. $individual_row_string .$n.
632             $tt.']';
633              
634             # Add properties
635 31 100       56 if ( $_->{'properties'} ) {
636 3         8 my $properties = $self->_encode_properties( $_->{'properties'} );
637 3         7 $output .= ',' .$n.$tt.'"p":' . $properties;
638             }
639              
640 31         48 $output .= $n.$t.$t.'}';
641 31         62 $output;
642             } @$rows;
643 22         45 my $rows_string = join ',' . $n . $t . $t, @rows;
644              
645 22         102 my $return =
646             '{' .$n.
647             $t. '"cols": [' .$n.
648             $t. $t. $columns_string .$n.
649             $t. '],' .$n.
650             $t. '"rows": [' .$n.
651             $t. $t. $rows_string .$n.
652             $t. ']';
653              
654 22 100       64 if ( defined $self->{'properties'} ) {
655 2         3 my $properties = $self->_encode_properties( $self->{'properties'} );
656 2         5 $return .= ',' .$n.$t.'"p":' . $properties;
657             }
658              
659 22         32 $return .= $n.'}';
660 22         149 return $return;
661             }
662              
663             sub _select_data {
664 22     22   33 my ($self, %params) = @_;
665              
666 22         723 my $rows = dclone $self->{'rows'};
667 22         29 my $columns = [map { $_->{'json'} } @{$self->{'columns'}}];
  59         109  
  22         47  
668              
669             # Select certain columns by id only
670 22 100 66     64 if ( $params{'columns'} && @{ $params{'columns'} } ) {
  13         42  
671 13         14 my @column_spec;
672              
673             # Get the name of each column
674 13         10 for my $column ( @{$params{'columns'}} ) {
  13         26  
675              
676             # And push it's place in the array in to our specification
677 13         16 my $index = $self->{'column_mapping'}->{ $column };
678 13 50       26 croak "Couldn't find a column named '$column'" unless
679             defined $index;
680 13         25 push(@column_spec, $index);
681             }
682              
683             # Grab the column selection
684 13         14 my @new_columns;
685 13         15 for my $index (@column_spec) {
686 13         13 my $column = splice( @{$columns}, $index, 1, '' );
  13         30  
687 13         21 push(@new_columns, $column);
688             }
689              
690             # Grab the row selection
691 13         18 my @new_rows;
692 13         18 for my $original_row (@$rows) {
693 14         13 my @new_cells;
694 14         14 for my $index (@column_spec) {
695 14         16 my $column = splice( @{$original_row->{'cells'}}, $index, 1, '' );
  14         22  
696 14         26 push(@new_cells, $column);
697             }
698 14         11 my $new_row = $original_row;
699 14         21 $new_row->{'cells'} = \@new_cells;
700              
701 14         27 push(@new_rows, $new_row);
702             }
703              
704 13         14 $rows = \@new_rows;
705 13         20 $columns = \@new_columns;
706             }
707              
708 22         54 return ( $columns, $rows );
709             }
710              
711             sub _encode_properties {
712 5     5   5 my ( $self, $properties ) = @_;
713 5         7 return $self->json_xs_object->encode( $properties );
714             }
715              
716             =head1 JSON vs Javascript
717              
718             Please note this module outputs Javascript, and not JSON. JSON is a subset of Javascript,
719             and Google's API requires a similar - but different - subset of Javascript. Specifically
720             some values need to be set to native Javascript objects, such as (and currently limited to)
721             the Date object. That means we output code like:
722              
723             {"v":new Date( 2011, 2, 21, 2, 6, 25 )}
724              
725             which is valid Javascript, but not valid JSON.
726              
727             =head1 DATES AND TIMES
728              
729             Dates are one of the reasons this module is needed at all - Google's API in
730             theory accepts Date objects, rather than a JSON equivalent of it. However,
731             given:
732              
733             new Date( 2011, 2, 21, 2, 6, 25 )
734              
735             in Javascript, what timezone is that? If you guessed UTC because that would be
736             The Right Thing To Do, sadly you guessed wrong - it's actually set in the
737             timezone of the client. And as you don't know what the client's timezone is,
738             if you're going to actually use this data for anything other than display to
739             that user, you're a little screwed.
740              
741             Even if we don't attempt to rescue that, if you pass in an Epoch timestamp, I
742             have no idea which timezone you want me to use to convert that in to the above.
743             We started off using C, which shows I hadn't really thought about it,
744             and will continue to use it for backwards compatibility, but:
745              
746             B. Either do the conversion in your
747             code using C or C, or pass in a L object whose
748             C<<->hour>> and friends return the right thing.
749              
750             We accept four types of date input, and this is how we handle each one:
751              
752             =head2 epoch seconds
753              
754             We use C, and then drop the returned fields straight in to a call to
755             C in JS.
756              
757             =head2 DateTime and Time::Piece
758              
759             We use whatever's being returned by C, C and C. Timezone messin'
760             in the object itself to get the output you want is left to you.
761              
762             =head2 Raw values
763              
764             We stick it straight in as you specified it.
765              
766             =head2 ... and one more thing
767              
768             So it is actually possible - although a PITA - to create a Date object in
769             Javascript using C which has an offset. In theory, all browsers
770             should support dates in L:
771              
772             Thu, 01 Jan 1970 00:00:00 GMT-0400
773              
774             If you're thinking L at
775             this point, you're on the right track...
776              
777             So here's the deal: B you specify C to this module's C
778             AND you pass in a L object, you'll get dates like:
779              
780             new Date("Thu, 01 Jan 1970 00:00:00 GMT-0400")
781              
782             in your output.
783              
784             =head1 BUG BOUNTY
785              
786             Find a reproducible bug, file a bug report, and I (Peter Sergeant) will donate
787             $10 to The Perl Foundation (or Wikipedia). Feature Requests are not bugs :-)
788             Offer subject to author's discretion...
789              
790             $20 donated 31Dec2010 to TPF re L
791              
792             $10 donated 11Nov2010 to TPF re L
793              
794             =head1 SUPPORT
795              
796             If you find a bug, please use
797             L
798             to raise it, or I might never see.
799              
800             =head1 AUTHOR
801              
802             Peter Sergeant C on behalf of
803             L - I
804             your market is thinking>.
805              
806             =head1 SEE ALSO
807              
808             L
809              
810             L - The underlying module
811              
812             L.
813              
814             L
815              
816             =head1 COPYRIGHT
817              
818             Copyright 2010 Investor Dynamics Ltd, some rights reserved.
819              
820             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
821              
822             =cut
823              
824             1;