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