Dear Monks,

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.

  1. A list passed to a table(), Tr() or td() method will be surrounded by the corresponding HTML element.
  2. An array-ref passed to a table(), Tr() or td() method will have every element surrounded by the corresponding HTML element.
  3. A hash-ref passed as the first argument to any method will be used as the HTML attributes for that element.

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


In reply to CGI.pm HTML-Generation Methods Considered Useful by friedo

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.