I would like to take some time to address an issue dear to my heart: CGI.pm's HTML-generation methods. Lest you rise up in righteous indignation, let me be clear that I in no way advocate implimenting an entire web application with them. However, it should be pointed out that in certain limited circumstances, they can be exceptionally useful. I wish to present a few tips and tricks for quickly and painlessly generating large amounts of HTML using CGI.pm.
Have you ever had a large data structure (maybe coming from a database, or maybe not) which needed to be converted to HTML quickly? CGI's table methods have a few handy features which can be exploited mercilessly to massage almost any data into a table with a single statement.
But you probably knew that already. Let's see how we can take advantage of it.
$html .= $q->table( { -bgcolor => '#ffffff', -border => 1 }, $q->caption('Status Summary'), # beware beautiful nested maps ahead $q->Tr( [ map { my $k = $_; $q->td( [ $k, map { $data->{$k}{$_} } sort keys %{ $data->{$k} } ] ) } sort keys %{ $data } ] ) );
This example comes from a simple status monitor application I wrote. In the example, $data is a hash-reference which points to a two-level hash consisting of a machine name and a series of tests run on that machine. For example, $data->{machine1}{pload} contains the status of the processor load on machine1.
The code generates a simple table with the machines as rows and the tests as columns, and the status data in each table cell. The inner map fills an array reference with items to put in <td> tags. The first item is the outer key, the machine name, in $k The ramaining items are the status of each test for that machine. The outer map then creates an array reference of those rows to pass back to Tr(), which builds the rows and is then passed to table(), completing our HTML. This code will work regardless of the number of machines or tests, as long as the form of the data structure is kept the same.
But we can do better. Suppose we want to highlight a cell if a machine is reporting dangerous processor load? By adding an inline conditional we can optionally pass style elements to the cell based dynamically on the data therein.
$html .= $q->table( { -bgcolor => '#ffffff', -border => 1 }, $q->caption('Status Summary'), # beware beautiful nested maps ahead map { my $k = $_; $q->Tr( $q->td( $k ), map { $q->td( $data->{$k}{$_} eq 'bad' ? { -bgcolor => '#ff0000' } : { -bgcolor => '#ffffff' }, $data->{$k}{$_} ) } sort keys %{ $data->{$k} } ) } sort keys %{ $data } );
Here we have switched from passing array references of items to surround with an HTML element to passing lists of individual elements. This is so we can pass individual hash-refs with style elements to each table cell. The conditional at the beginning of our calls to $q->td() checks to see if the status is 'bad'. If it is, we make the cell background red, if not, we make it white.
These two examples just scratch the surface of what can be done quickly and easily with CGI.pm's nested method calls. The above two examples took less than ten minutes (most of the time spent fixing errant braces and parantheses.) Each example can be expanded; for instance, another conditional could be added to allow a cell datum to be a hyperlink to additional data in certain circumstances. Figuring out a cute way to include the table headers is left as an exercise for the reader.
Caveats: These methods are great for quickly and painlessly generating tables from arbitrary data, using Perl's infinitely powerful list handling capabilities. You should not rely on them to develop a complete web or database application, for which you really should use a templating system of some sort. Also, depending on how complex you get, the code can be very difficult to read, especially for novices who are not familiar with map and/or references.
Code in peace. May the source be with you.
--Mike Friedman
|
|---|