package CGI::Tables;
use strict;
use vars qw[@ISA @EXPORT $VERSION];
$VERSION = '0.9';
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(table);
#feedit: options, data
#receive: html
#effect: none
sub table {
my $data = pop;
my $keyed = { @_ };
my $html;
my %attrs = %{shift @$data}
if "HASH" eq ref $data->[0];
$data = arrange($data, $keyed)
if $keyed->{cols} or $keyed->{rows};
$data = add_titles($keyed, $data)
if $keyed->{col_titles} or $keyed->{row_titles};
$html .= "
\n";
$html .= row(@$_) for @$data;
$html .= "
\n";
return $html;
}
#feedit: row data
#receive: html
#effect: none
sub row {
my (@cells) = @_;
my %attrs = %{shift @cells}
if "HASH" eq ref $cells[0];
my $html = "";
$html .= cell($_) for @cells;
$html .= "
\n";
return $html;
}
#feedit: cell data
#receive: html
#effect: none
sub cell {
my ($data) = @_;
return "$data | " if not ref $data;
my %attrs = %{shift @$data}
if "HASH" eq ref $data->[0];
my $html = "";
$html .= $_ for @$data;
$html .= " | ";
$html =~ s/td/th/g
if $attrs{_header};
return $html;
}
#feedit: keyed args, data
#receive: data
#effect: none
sub add_titles {
my ($keyed, $data) = @_;
my @titled;
if ($keyed->{col_titles}) {
my @titles;
push @titles, title($keyed->{col_titles}, $_)
for 0..$#{$data->[0]};
push @titled, [@titles];
}
if ($keyed->{row_titles}) {
unshift @{$titled[0]}, ""
if $keyed->{col_titles};
for (0..$#$data) {
push @titled, [ title($keyed->{row_titles}, $_),
@{$data->[$_]} ];
}
} else {
push @titled, @$data;
}
return \@titled;
}
#feedit: titles, col/row number
#receive: title
#effect: none
sub title {
my ($titles, $n) = @_;
my $title;
if ("ARRAY" eq ref $titles) {
$title = $titles->[$n];
}
elsif ("CODE" eq ref $titles) {
$title = $titles->($n);
}
return [ {_header=>1}, $title ];
}
#feedit: attribute hash
#receive: code for inside html tags
#effect: none
sub attributes {
my (%attrs) = @_;
my $html = "";
for (keys %attrs) {
next if $_ =~ /^_/;
$html .= " $_=\"$attrs{$_}\"";
}
return $html;
}
#feedit: data, keyed
#receive: arranged data
#effect: none
sub arrange {
my ($data, $keyed) = @_;
my ($rows, $cols) = size($#$data + 1, $keyed);
# add extra cells that are needed
push @$data, ""
until $#$data + 1 == $rows * $cols;
my @arranged;
if ($keyed->{cols}) {
for (1..$rows) {
push @arranged, [ splice @$data, 0, $cols ];
}
} else {
push @arranged, [] for 1..$rows;
while (@$data) {
push @$_, shift @$data
for @arranged;
}
}
return \@arranged;
}
#feedit: number of data elements, keyed
#receive: number of rows, cols
#effect: none
sub size {
my ($n_data, $keyed) = @_;
my ($rows, $cols, $ratio);
if ($rows = $keyed->{rows}) {
$cols = divisor($n_data, $rows);
}
elsif ($cols = $keyed->{cols}) {
$rows = divisor($n_data, $cols);
}
return $rows, $cols;
}
#feedit: number of data elements, number of (rows, cols)
#receive: number of other way
#effect: none
sub divisor {
my ($n_data, $rows) = @_;
my $cols = int $n_data / $rows;
$cols++ if $n_data % $rows;
return $cols;
}
1;
__END__
=head1 NAME
CGI::Tables - An easy way to create HTML tables
=head1 SYNOPSIS
use CGI::Tables;
print table cols => 2, [qw(Cell Cell Cell)];
=head1 DESCRIPTION
Only one function is exported: C. You pass it the parameters and
the data, in that order. The data is passed as an array ref and the
parameters as a list. In return, you get HTML for a table.
=head2 Arranging Data
There are three ways to set up the data. All of the examples in this
section produce identical tables.
=over
=item Hard Coding
This method is the old fashioned way. If you're lazy (it's a virtue), use
one of the other two. This I the most efficient way though. Pass the
rows as array refs and the cells inside that.
table [ [ "Row 1 Cell 1", "Row 1 Cell 2", "Row 1 Cell 3" ],
[ "Row 2 Cell 1", "Row 2 Cell 2", "Row 2 Cell 3" ] ];
=item Cols
If you pass the number of columns you want and put the cells in the data
array ref, CGI::Tables will automagically figure out how many rows there
are and place the data in the correct place. The cells go from left to right,
top to bottom.
table cols => 3, [ "Row 1 Cell 1", "Row 1 Cell 2",
"Row 1 Cell 3", "Row 2 Cell 1",
"Row 2 Cell 2", "Row 2 Cell 3" ];
=item Rows
This is similar to passing the number of columns. The only difference
is that the data as arranged top to bottom, left to right.
table rows => 2, [ "Row 1 Cell 1", "Row 2 Cell 1",
"Row 1 Cell 2", "Row 2 Cell 2",
"Row 1 Cell 3", "Row 2 Cell 3" ];
=back
=head2 Cells
When passing a cell, it is perfectly acceptable to pass a string. You can pass
an array ref if you so desire. The elements of ref will be concatenated
and placed in the cell.
=head2 Column and Row Headers
Column and row headers are passed by C and C,
respectively. You can either pass an array ref of titles or a code/subroutine
reference. Code refs are passed column/row indices, which start at zero.
Examples:
table rows => 2, col_titles => sub { return "Col " . (shift() + 1) },
[ @data ];
table rows => 2, row_titles => ["Row 1", "Row 2"],
[ @data ];
=head2 Specifying HTML Attributes
HTML attributes can be specifed for the table, a row, or a cell. Stick a
hash ref as the first element in the appropriate array ref, and it will be
added. For cells, pass an array ref (which would otherwise be optional)
with a hash ref in front. Row attributes cannot be passed when the table is
created dynamically. The following example will print a table of width
600 with one cell that has #660000 as the background color.
table cols => 2, [ {width => 600}, [ {bgcolor => "#660000"}, "Cell 1" ],
"Cell 2", "Cell 3", "Cell 4" ];
=head1 AUTHOR
Matthew Diephouse
matt@diephouse.com
=cut