I often find myself generating HTML tables from SQL tables, with some amount of formatting and munging in between. A handy technique that I use often is to put as much of the column description into one place as I can, then have a general loop that I can apply to each row of data. This lets me tweak many settings in one place and avoids lots of redundancy (in both perl and HTML).

# since the order of the columns is significant, we # use an array to hold information about each column. my @COL_INFO = ( { name => 'foo_id', hide => 1 }, { name => 'foo', id => 'foo_id', width => 400, gen => \&gen_foo, title => 'Foo' }, { name => 'bar', width => 200, align => 'right', title => 'Barque' } );

If I need other utility arrays or hashes, I can generate them all from the one central store above:

# now we can derive whichever other hashes or arrays # we might want from the above data. having a list # of column names is always handy: my @COL_NAMES = map $_->{name}, @COL_INFO; # and we might want to look up columns by name: my %COL_BY_NAME; @COL_BY_NAME{ @COL_NAMES } = @COL_INFO; # and to find the index of a column name: my %COL_INDEX; @COL_INDEX{ @COL_NAMES } = 0 .. $#COL_INFO;

This degree of uniformity lets us use very regular loops to print out headers and rows, and do all our checking and formatting in one place:

# ------------------------------------------------------- # the information is centralized for other uses, such as # printing headers: print "<tr>"; foreach my $info ( @COL_INFO ) { # skip hidden fields next if $info->{hide}; # build up opening tag my $header = "<th"; if ( my $width = $info->{width} ) { $header .= qq| width="$width"|; } $header .= ">"; print( $header . escapeHTML( $info->{title} ) . "</th>\n" ); } print "</tr>\n"; # ------------------------------------------------------- # then for each row: while ( my $cur = $sth->fetch() ) { # if necessary, you can construct a hash using # whatever keys you want, not just db column names: my %row; @row{ @COL_NAMES } = @$cur; # or maybe just pick out one value my $foo_id = $cur->[ $COL_INDEX{foo_id} ]; # formatting the output is now easier, too: print qq|<tr valign="top">\n|; foreach my $info ( @COL_INFO ) { my $cell = "<td"; $cell .= qq| align="$info->{align}"| if $info->{align}; $cell .= ">"; if ( my $gen = $info->{gen} ) { $cell .= $gen->( $cur ); } else { my $ix = $COL_INDEX{ $info->{name} }; $cell .= escapeHTML( $cur->[$ix] ); } $cell .= "</td>\n"; print $cell; } print "</tr>\n"; }

I like this centralized mechanism because I only have to change one place to add or remove a column, and I only have to code tricky things (remembering to HTML-entify strings, etc) in one place. Also, the description itself could eventually be extracted from a database, leading to even more dynamic (while still "typesafe") and well-formatted output.

Edit by BazB: added readmore tags.

Replies are listed 'Best First'.
Re: One structure to describe multiple arrays or hashes
by dragonchild (Archbishop) on Apr 28, 2004 at 12:45 UTC
    You know, if you used Template Toolkit, 90% of this code could be foisted off to the template and it would "Do The Right Thing"<super>TM</super>, including HTML-entification, formatting, and the like. In fact, there's an example in the Template Toolkit book about how to have your template get its data from a database.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

        Yes, it does, and the authors specifically state that having the template do all the heavy lifting violates standard practice for large applications. However, they do state that, for smaller sites, this can be beneficial.

        In addition, having your templates talk to a database doesn't violate MVC, in and of itself. Let's take a real-world example that I have worked on - a site that is available in 12 languages. Obviously, what language you present in is a V-layer issue. So, when you want to specify the column names for your report, you indicate in your code (either in the template or in the script) that you want the names for column 1 .. 5 in language $lang. The template would then lookup in a front-end database what the actual strings are for those columns in that language.

        Contrast this approach with the one that was taken. They were using HTML::Template, not Template Toolkit. They had a master template with the column specifiers. They then had a pre-processing step (kinda like compiling templates) which converted the master into 12 copies, one for each language. The actual strings were stored in a database that would be consulted when the templates were compiled.

        I know which method I prefer ...

        ------
        We are the carpenters and bricklayers of the Information Age.

        Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

      You know, if you used Template Toolkit, 90% of this code could be foisted off to the template

      That still leaves 10% that I would have to deal with in my own code, so I would need a structure like this anyway. (Not to mention that I find doing anything fancier than straight substitution in template systems to end up as a frustrating exercise in creating a lousy language to avoid using a real one. No thank you...)

      Also, although my example used DBI as a source and HTML as an output, this technique is not limited to those technologies.

      Finally, one of the things I like about this techinique is that it centralizes the description and differences between all the columns in one place. If I want to rearrange columns in this scheme, I just swap hrefs around; using a template system, I need to change it in the template and in the loop.

      I totally agree that templates are very handy when you have different people working on the code and the template. When it is just one person, I find it easier to have all the control in one place.