|
HTML::Tabulate - HTML table rendering class |
HTML::Tabulate - HTML table rendering class
use HTML::Tabulate qw(render);
# Setup a simple table definition hashref
$table_defn = {
table => { border => 0, cellpadding => 0, cellspacing => 3 },
th => { class => 'foobar' },
null => ' ',
labels => 1,
stripe => '#cccccc',
};
# Render a dataset using this table definition (procedural version)
print render($dataset, $table_defn);
# Object-oriented version
$t = HTML::Tabulate->new($table_defn);
print $t->render($dataset);
# Setup some dataset specific settings
$table_defn2 = {
fields => [ qw(emp_id name title edit new_flag) ],
field_attr => {
# format employee ids, add a link to employee page
emp_id => {
format => '%-05d',
link => "emp.html?id=%s",
link_target => '_blank',
align => 'right',
},
# uppercase all names
qr/name$/ => { format => sub { uc(shift) } },
# highlight new employees
new_flag => {
class => sub {
my ($data, $row, $field) = @_;
$data =~ m/^y$/i ? 'new', 'old';
},
},
},
};
# Render the table using the original and additional settings
print $t->render($data, $table_defn2);
HTML::Tabulate is used to render/display a given set of data in an HTML table. It takes a data set and a presentation definition and applies the presentation to the data set to produce the HTML table output. The presentation definition accepts arguments corresponding to HTML table tags ('table', 'tr', 'th', 'td' etc.), to define attributes for those tags, plus additional arguments for other aspects of the presentation. HTML::Tabulate supports advanced features like automatic striping, arbitrary cell formatting, link creation, etc.
Presentation definitions can be defined in multiple passes, which are progressively merged, allowing general defaults to be defined in common and then overridden by more specific requirements. Presentation definitions are stored in the current object, except for those defined for a specific 'render', which are temporary.
Supported data sets include arrayrefs of arrayrefs (DBI
selectall_arrayref, for example), arrayrefs of hashrefs, a simple
hashref (producing single row tables), or iterator objects that
support first() and next() methods (like DBIx::Recordset objects or
Class::DBI iterators).
By default arrayref-based datasets are interpreted as containing successive table rows; a column-based interpretation can be forced using style => 'across'.
The primary interface is object-oriented, but a procedural interface is also available where the extra flexibility of the OO interface is not required.
table => { border => 0, cellpadding => 3, align => 'center' }
$sub->( $row )
where $row is a reference to the data row, and the result is used as the attribute value. e.g.
tr => {
class => sub {
my $r = shift; my $name = $r->[1]; $name =~ s/\s+/_/; lc $name
},
},
will set the 'class' attribute on the 'tr' to be a lowercased underscored version of $r->[1].
Two additional tbody styles are supported. If a '``-field'' => ``FIELDNAME''' element exists in the tbody hashref, then the table body will be broken into tbody sections whenever the value of the given field changes (does not necessarily need to be a displayed field, of course) e.g.
tbody => { '-field' => 'emp_gender' }
If a '``-rows'' => NUMBER' element exists in the tbody hashref, the table body will be broken into tbody sections every NUMBER rows. e.g.
tbody => { '-rows' => 25 }
$sub->( $data, $row, $field )
and the result used as the attribute value. The arguments are: $data is the (label) value; $row is a reference to the entire row; and $field is the name of the field (so subreferences can be potentially used for more than one field).
For example, given the following set of labels on a table:
'Emp ID', 'Emp Name', 'Emp Title', 'Emp Birth Dt'
you could define a class attribute to the <th> tag by doing:
th => {
class => sub {
my ($d, $r, $f) = @_;
$d =~ m/^Emp //;
$d =~ m/\s+/_/g;
lc $d
},
}
which would give a th line like (line breaks added for clarity):
<tr> <th class="id">Emp ID</th> <th class="name">Emp Name</th> <th class="title">Emp Title</th> <th class="birth_dt">Emp Birth Dt</th> </tr>
$sub->( $data, $row, $field )
and the result used as the attribute value. See the preceding the th manpage item for further explanation and discussion.
fields => [ qw(emp_id emp_name emp_title emp_birth_dt) ]
If 'fields' is not defined at render time and the dataset is not array-based, HTML::Tabulate will attempt to derive a useful default set from your data, and croaks if it is not successful.
fields_add => {
emp_name => [ 'emp_givenname', 'emp_surname' ],
emp_birth_dt => 'edit',
}
applied to a fields list qw(emp_id emp_name emp_title emp_birth_dt) produces a composite field list containing:
qw(emp_id emp_name emp_givenname emp_surname emp_title
emp_birth_dt edit)
fields_omit => [ qw(emp_modify_ts emp_create_ts) ]
in_fields => [ qw(emp_id emp_title emp_birth_dt emp_title) ]
Using in_fields only makes sense if the dataset rows are arrayrefs.
# Turn labels on, derived from field names, or defined per-field labels => 1
labels => { emp_id => 'Emp ID' },
label_links => { emp_id => "me.html?order=%s" }
will create a label for the emp_id field of:
<a href="me.html?order=emp_id">Emp ID</a>
# alternate grey and default bgcolor bands stripe => '#999999'
# successive red, green, and blue stripes stripe => [ '#cc0000', '#00cc00', '#0000cc' ]
Stripes that are hashrefs or an arrayref of hashrefs are rendered as attributes to the <tr> tags on the rows to which they apply. Similarly to scalars, single hashrefs are applied to every second <tr> tag, beginning with the label/header row, while multiple hashrefs in an arrayref are applied to successive rows, cycling though the array before beginning again. e.g.
# alternate stripe and default rows
stripe => { class => 'stripe' }
# alternating between two stripe classes
stripe => [ { class => 'stripe1' }, { class => 'stripe2' } ]
# Replace all empty fields with non-breaking spaces null => ' '
qr() regular expressionsThese are always merged in the order above, allowing defaults to be defined for all fields, overridden for fields matching particular regexes, and then overridden further per-field. e.g.
# Align all fields left except timestamps (*_ts)
field_attr => {
-defaults => { align => 'left' },
qr/_ts$/ => { align = 'center' },
emp_create_ts => { label => 'Created' },
},
Field attribute arguments are discussed in the following section.
$format->($value, $dataset, $type)
(where $type is 'title') and should return the formatted title string to be used.
Subref titles are similar, except there is no separate title string involved; they are called with the following arguments:
$title->($dataset, $type);
(where $type is 'title') and should return the formatted title string to be used.
Examples:
# rendered: <h2>Employee Data</h2>
title => 'Employee Data'
# rendered: <h3 class="red_white_blue">Employee Data</h3>
title => {
value => 'Employee Data',
format => '<h3 class="red_white_blue">%s</h3>',
}
# rendered (e.g.): <h2>Employee Data (3 records)</h2>
title => sub {
my ($set, $type) = @_;
my $title = 'Employee Data';
$title .= ' (' . scalar(@$set) . ' records)'
if ref $set eq 'ARRAY';
sprintf '<h2>%s</h2>', $title;
}
From version 0.26, a new 'caption_caption' type is supported, which is rendered as a <caption> attribute on the table (with presentation typically controlled via css). To force this type, you should use a hashref caption argument, with an explicit type of 'caption_caption'. See below for examples.
For backward compatibility, the default is old-style type => 'caption'. That will change in a future release.
For example:
# Old style text caption, rendered below table
# rendered <p>Employee Data</p> (below table)
caption => 'Employee Data'
# rendered <div class="emp_data">Employee Data</div> (below table)
caption => {
value => 'Employee Data',
format => '<div class="emp_data">%s</div>',
}
# rendered (e.g.): <p>Employee Data (3 records)</p> (below table)
caption => sub {
my ($set, $type) = @_;
my $caption = 'Employee Data';
$caption .= ' (' . scalar(@$set) . ' records)'
if ref $set eq 'ARRAY';
sprintf '<p>%s</p>', $caption;
}
# New-style <caption> caption, rendered within table
# rendered <caption>Employee Data</caption> (within table)
caption => {
type => 'caption_caption',
value => 'Employee Data',
}
# rendered (e.g.): <caption>Employee Data (3 records)</caption> (within table)
caption => {
type => 'caption_caption',
value => 'Employee Data',
format => sub {
my ($caption, $set, $type) = @_;
$caption .= ' (' . scalar(@$set) . ' records)'
if ref $set eq 'ARRAY';
$caption
}
}
field_attr => {
emp_id => {
align => 'center',
valign => 'top',
class => sub { my ($d, $r, $f) = @_; $f =~ s/^emp_//; $f },
}
}
will cause emp_id table cells to be displayed as:
<td align="center" class="id" valign="top">
Attribute values may be either scalar, which are used directly, or subroutine references, which are called with the following arguments:
$sub->( $data, $row, $field )
and the result used as the attribute value. The arguments are: the (unformatted) data value; a reference to the entire data row; and the field name (so subreferences can be potentially used for more than one field).
$sub->( $data, $row, $field )
and the result used as the data value. The arguments are: the original data value itself; a reference to the entire data row; and the field name (so subrefs can potentially be used for more than one field).
This allows the value to be modified or set according to the current value, or based on any other value in the row (or anything else, for that matter) e.g.
# Derive emp_fname from first word of emp_name
field_attr => {
emp_fname => {
value => sub {
my ($data, $row, $field) = @_;
if ($row->{emp_name} =~ m/^\s*(\w+)/) { return $1; }
return '';
},
},
edit => { value => 'edit' },
}
field_attr => {
emp_id => {
link => 'emp.html?id=%s',
format => '%05d',
},
}
creates a link in the table cell like:
<a href="emp.html?id=1">00001</a>
Note that links are not created for labels/headings - to do so use the separate label_link argument below.
field_attr => {
emp_id => {
link => 'emp.html?id=%s',
link_class => sub { my ($d, $r, $f) = @_; "class_$f" },
link_target => '_blank',
link_title => 'Employee details',
},
}
creates a link in the table cell like:
<a class="class_emp_id" href="emp.html?id=123" target="_blank" title="Employee details">123</a>
field_attr => {
emp_id => {
label => 'Emp ID',
label_link => sub { my ($d, $r, $f) = @_; "?order=$f" },
label_link_target => '_blank',
label_link_title => sub { my ($d, $r, $f) = @_; "Order by $d" },
},
}
creates a link for the label like:
<a href="?order=emp_id" target="_blank" title="Order by Emp ID">Emp ID</a>
HTML::Tabulate has three main public methods:
new($table_defn)merge($table_defn)new()
and merge() methods are persistent across renders.
render() can also be used procedurally if explicitly imported:
use HTML::Tabulate qw(render); print render($dataset, $table_defn);
HTML::Tabulate supports the following dataset types:
First() and Next() or first() and next() then HTML::Tabulate will use
those methods to walk the dataset. DBIx::Recordset objects and
Class::DBI iterators definitely work; beyond those your mileage
may vary - please let me know your successes and failures.
HTML::Tabulate is intended to be easy to subclass, to allow you to setup sensible defaults for site-wide use, for instance. Something like this seems to work well:
package My::Tabulate;
use base qw(HTML::Tabulate);
sub new {
my $class = shift;
my $defn = shift || {};
my %defaults = (
# define table defaults here e.g.
table => { border => 1 },
labels => { foo => 'FOO', bar => 'BAR' },
);
my $self = $class->SUPER::new(\%defaults);
$self->merge($defn);
return $self;
}
1;
Probably. Please let me know if you find something going awry.
Is now much bigger and more complicated than was originally envisaged. Needs to be completely refactored. Sometime.
Gavin Carr <gavin@openfusion.com.au>
Copyright 2003-2006, Gavin Carr. All Rights Reserved.
This program is free software. You may copy or redistribute it under the same terms as perl itself.
|
HTML::Tabulate - HTML table rendering class |