The current node continues the exploration of tree-based HTML generation and simultaneously provides a discussion of the merits of DWIM-programming with 2 relevant examples.
package Simple::Class; my @name = qw(bob bill brian babette bobo bix); my @age = qw(99 12 44 52 12 43); my @weight = qw(99 52 80 124 120 230); sub new { my $this = shift; bless {}, ref($this) || $this; } sub load_data { my @data; for (0 .. 5) { push @data, { age => $age[rand $#age] + int rand 20, name => shift @name, weight => $weight[rand $#weight] + int rand 40 } } \@data; }
Very straightforward: just five rows of tabular data
Again, pretty simple, we just want to replace our sample table with real rows of table data.<table id="load_data"> <tr> <th>name</th><th>age</th><th>weight</th> </tr> <tr id="iterate"> <td id="name"> NATURE BOY RIC FLAIR </td> <td id="age"> 35 </td> <td id="weight"> 220 </td> </tr> </table>
use HTML::Seamstress; # load the view my $seamstress = HTML::Seamstress->new_from_file('simple.html'); # load the model my $o = Simple::Class->new; my $data = $o->load_data; # find the <table> and <tr> my $table_node = $seamstress->look_down('id', 'load_data'); my $iter_node = $table_node->look_down('id', 'iterate'); my $table_parent = $table_node->parent; # drop the sample <table> and <tr> from the HTML # only add them in if there is data in the model # this is achieved via the $add_table flag $table_node->detach; $iter_node->detach; my $add_table; # Get a row of model data while (my $row = shift @$data) { ++$add_table; # clone the sample <tr> my $new_iter_node = $iter_node->clone; # find the tags labeled name age and weight and # set their content to the row data $new_iter_node->content_handler($_ => $row->{$_}) for qw(name age weight); # push this table row onto the table $table_node->push_content($new_iter_node); } # reattach the table to the HTML tree if we loaded data into some tabl +e rows $table_parent->push_content($table_node) if $add_table; print $seamstress->as_HTML;
So that was a job done the seamstress way. Then I thought that I didnt want to handcode all of those stereotyped tree operations each time I wanted to unroll tabular data into a tabular view so I needed an API call that would do it for me.
Note how the programmer simply said computeArea without having to figure out what type of shape he was dealing with: he simply told the program to do what he wanted and hid/saved himself from the details.use Inline::Java; # grin ;-) Shape[] shapes = new Shape[100]; shapes[0] = new Rectange(45,27); shapes[1] = new Circle(10); shapes[2] = new Ellipse(25,13); shapes[3] = new Quadrilateral(15,20,50,14); ... double area = 0; for (int i=0; i < shapes.length; i++) area += shapes[i].computeArea();
I will discuss how I made use of DWIM techniques to simplify the a subroutine to unroll a model and view into a concrete table. My first cut at this function was OK. Here is the view (again):
and here is is how it was called:<table id="load_data"> <tr> <th>name</th><th>age</th><th>weight</th> </tr> <tr id="iterate"> <td id="name"> NATURE BOY RIC FLAIR </td> <td id="age"> 35 </td> <td id="weight"> 220 </td> </tr> </table>
use HTML::Seamstress; # load the view my $seamstress = HTML::Seamstress->new_from_file('simple.html'); # load the model my $o = Simple::Class->new; $seamstress->table ( # tell seamstress where to find <table>, via the method call # ->look_down('id', $gi_table). Seamstress detaches the table from +the # HTML tree automatically if no table rows can be built # "gi" stands for generic identifier. Most people call "gi"s tags, +but # mirod calls them "gi"s so I will too :) gi_table => 'load_data', # tell seamstress where to find the <tr>. # this is the major place where DWIM will come in! gi_tr => 'iterate', # the model data to be pushed into the table, row by row table_data => $o->load_data, # The way to take the model data and obtain one row. # If the table data were a hashref, we would do: # my $key = (keys %$data)[0]; my $val = $data->{$key}; delete $data +->{$key} tr_data => sub { my ($self, $data) = @_; shift(@{$data}) ; }, # the way to take a row of data and fill the <td> tags # content_handler() is a Seamstress function which takes # $id_val and $content as args. It does a ->look_down('id', $id_val +) # and sets the content section of the found node (a <td>) to $conte +nt td_data => sub { my ($tr_node, $tr_data) = @_; $tr_node->content_handler($_ => $tr_data->{$_}) for qw(name age weight) } ); print $seamstress->as_HTML;
So, I wrote a ->table() function which received all the information on how to find and iterate the view and model and turned the dummy table in the HTML into a real table, filled out with model data.
Then I decided that this function should be flexible as a function of whether the gi_tr argument was a simple scalar or an array ref. If a simple scalar was passed in, then that <tr> would serve as the model for all <tr> in the table. If an array ref was passed in then each element of the array ref would serve as a model <tr> in sequence. Practically speaking, this is the way to get alternating table rows with different background colors.
Here is the sample HTML that we would use to model alternating table rows:
<table id="load_data" CELLPADDING=8 BORDER=2> <tr> <th>name</th><th>age</th><th>weight</th> </tr> <tr id="iterate1" BGCOLOR="white" > #### ONE BGCOLOR <td id="name"> NATURE BOY RIC FLAIR </td> <td id="age"> 35 </td> <td id="weight"> 220 </td> </tr> <tr id="iterate2" BGCOLOR="#CCCC99"> ### ANOTHER BGCOLOR <td id="name"> NATURE BOY RIC FLAIR </td> <td id="age"> 35 </td> <td id="weight"> 220 </td> </tr> </table>
The only difference in the API call is that
will have to be changed togi_tr => 'iterate',
So that Seamstress can find the list of dummy rows instead of just one row.gi_tr => [qw(iterate1 iterate2)],
but within my code, I have a lot to do to handle the scalar versus array ref case... unless I can fold both the scalar and arrayref into the same data structure and treat them the same regardless of which comes in.
My first task was to promote the scalar to a list and fold the array ref to a list:
sub table { my ($s, %table) = @_; my @table_gi_tr = listify $table{gi_tr} ; ...
Voila. Now, regardless of whether the input scalar is a simple scalar or an array ref, I have an array ref. If I did not have the DWIM-ease of Scalar::Listify here is what I would have had to write:
my @table_gi_tr = (ref $table{gi_tr} eq 'ARRAY') ? (@$table{gi_tr}) : ( $table{gi_tr})
...err, 3 Perl expressions with more brackets and colons and questions marks than the Sunday comic versus one succinct DWIM API call? I will take the Scalar::Listify DWIM version anyday, thank you.
So, now it does not matter to the rest of the subroutine whether a scalar (one tr) or array ref (of several tr) was passed in, because we have turned either into an array.
Now comes potential stumbling block #2: how to cycle through a list of table rows, repeating the list when we come to the end? The answer: Tie::Cycle by brian d. foy. We cycle blindly in a DWIM fashion. We cycle over our 1-element list just as gleefully as we do our 2-element list of <tr>. Hell, make it 3 or 4 rows, it just don't matter. cycle, cycle, cycle:
And so, while we iterate through the row data of the model, Tie::Cycle keeps feeding us the proper row of the sample view <tr>s for Seamstress to rip through and load up the row with model data.# fold the scalar and aref into an array my @table_gi_tr = listify $table{gi_tr} ; # create an array containing HTML::Element pointers to the <tr> or <tr +>s my @iter_node = map { $table->{table_node}->look_down($ID, $_) } @table_gi_tr; # tie a scalar to the list of <tr> so that we can cycle thru 'em tie $table->{iter_node}, 'Tie::Cycle', \@iter_node; # iterate through the model data while (my $row = $table{tr_data}->($table, $table{table_data})) { # pick out a <tr> to display the row of model data with: my $I = $table->{iter_node}; # force the tied data to FETCH in +to $I my $new_iter_node = $I->clone; # clone the dummy <tr> for templa +ting # wont work: my $new_iter_node = $table->{iter_node}->clone +; ... }
The final subroutine for your DWIM-viewing pleasure: handles simple tables and those with multiple sample-tr rows with equal poise:
our ($table_data, $tr_data, $gi_td); sub table { my ($s, %table) = @_; my $table = {}; $table->{table_node} = $s->look_down($ID, $table{gi_table}); my @table_gi_tr = listify $table{gi_tr} ; my @iter_node = map { $table->{table_node}->look_down($ID, $_) } @table_gi_tr; tie $table->{iter_node}, 'Tie::Cycle', \@iter_node; $table->{content} = $table{content}; $table->{parent} = $table->{table_node}->parent; $table->{table_node}->detach; $_->detach for @iter_node; my $add_table; while (my $row = $table{tr_data}->($table, $table{table_data})) { ++$add_table; # wont work: my $new_iter_node = $table->{iter_node}->clone +; my $I = $table->{iter_node}; my $new_iter_node = $I->clone; $table{td_data}->($new_iter_node, $row); $table->{table_node}->push_content($new_iter_node); } $table->{parent}->push_content($table->{table_node}) if $add_table; }
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: HTATR II: HTML table generation via DWIM tree rewriting
by jeffa (Bishop) on Oct 30, 2003 at 14:55 UTC | |
by princepawn (Parson) on Oct 30, 2003 at 15:36 UTC | |
by jeffa (Bishop) on Oct 30, 2003 at 16:21 UTC | |
by princepawn (Parson) on Oct 30, 2003 at 17:57 UTC | |
by cbraga (Pilgrim) on Oct 30, 2003 at 16:54 UTC | |
by jeffa (Bishop) on Oct 30, 2003 at 18:31 UTC | |
|
Re: HTATR II: HTML table generation via DWIM tree rewriting
by Anonymous Monk on Oct 30, 2003 at 22:25 UTC | |
by princepawn (Parson) on Oct 30, 2003 at 23:55 UTC | |
|
Re: HTATR II: HTML table generation via DWIM tree rewriting
by cbraga (Pilgrim) on Oct 30, 2003 at 16:51 UTC |