As I understand your question, you want to be able for table to output certain rows with an initial header (i.e. whead) and certain without (i.e. data), but you don't want the current limitation where all the whead rows must be at the end of the table. Is that correct?
If so, as a first step, I'd replace table's data and whead options with a single rows option which takes a single arrayref. This arrayref would contain a list of type/row pairs, where the type is a string "data" or "whead" and the row is an arrayref:
sub table { my ($tab,$opt) = @_; my @attributes; push @attributes, qq(id="$opt->{id}") if $opt->{id}; push @attributes, qq(class="$opt->{class}") if $opt->{class}; push @attributes, qq(style="$opt->{style}") if $opt->{style}; my $format = @attributes > 0 ? ' '.join(' ',@attributes) : ''; my $tag = 'table'.$format; line($tab,qq(<$tag>)); line($tab + 1,qq(<caption>$opt->{caption}</caption>)) if $opt->{capt +ion}; row($tab + 1,'header',$opt->{headings}) if $opt->{headings}; my $next_needs_head; for (@{$opt->{rows} || []}) { if (!ref and /^whead$/) { $next_needs_head = 1; next; } elsif (!ref) { $next_needs_head = 0; next; } row($tab + 1, $next_needs_head?"whead":"data", $_); $next_needs_head = 0; } line($tab,q(</table>)); } table(1, { id => 't1', headings => [qw/NAME STRENGTH COMMENT/], rows => [ whead => [qw/1 2 no/], data => [qw/2 3 yes/], whead => [qw/10 20 another/], data => [qw/100 200 comment/], ], });
As a next step, I'd abandon the type/row pairs idea, and use blessing so each row "knows" what type it is:
sub table { my ($tab,$opt) = @_; my @attributes; push @attributes, qq(id="$opt->{id}") if $opt->{id}; push @attributes, qq(class="$opt->{class}") if $opt->{class}; push @attributes, qq(style="$opt->{style}") if $opt->{style}; my $format = @attributes > 0 ? ' '.join(' ',@attributes) : ''; my $tag = 'table'.$format; line($tab,qq(<$tag>)); line($tab + 1,qq(<caption>$opt->{caption}</caption>)) if $opt->{capt +ion}; row($tab + 1,'header',$opt->{headings}) if $opt->{headings}; for (@{$opt->{rows} || []}) { row($tab + 1, $_->isa("MyRow::WithHead")?"whead":"data", $_); } line($tab,q(</table>)); } sub data { bless \@_, "MyRow" } sub whead { bless \@_, "MyRow::WithHead" } table(1, { id => 't1', headings => [qw/NAME STRENGTH COMMENT/], rows => [ whead(qw/1 2 no/), data( qw/2 3 yes/), whead(qw/10 20 another/), data( qw/100 200 comment/), ], });
Then I'd start pushing the logic for outputting data into the MyRow classes, and indeed into some MyCell classes:
sub table { my ($tab,$opt) = @_; my @attributes; push @attributes, qq(id="$opt->{id}") if $opt->{id}; push @attributes, qq(class="$opt->{class}") if $opt->{class}; push @attributes, qq(style="$opt->{style}") if $opt->{style}; my $format = @attributes > 0 ? ' '.join(' ',@attributes) : ''; my $tag = 'table'.$format; line($tab,qq(<$tag>)); line($tab + 1,qq(<caption>$opt->{caption}</caption>)) if $opt->{capt +ion}; row($tab + 1,'header',$opt->{headings}) if $opt->{headings}; $_->output($tab+1) for @{$opt->{rows} || []}; line($tab,q(</table>)); } { package MyCell; sub output { my $self = shift; my ($tab) = @_; ::line($tab, qq(<td>@$self</td>)); } } { package MyCell::Header; use base "MyCell"; sub output { my $self = shift; my ($tab) = @_; ::line($tab, qq(<th>@$self</th>)); } } { package MyRow; sub cells { my $self = shift; return map { bless [$_], "MyCell" } @$self; } sub output { my $self = shift; my ($tab) = @_; ::line($tab,qq(<tr>)); $_->output($tab + 1) for $self->cells; ::line($tab,qq(</tr>)); } } { package MyRow::WithHead; use base "MyRow"; sub cells { my $self = shift; my @cells = $self->SUPER::cells; bless $cells[0], "MyCell::Header"; return @cells; } } sub data { bless \@_, "MyRow" } sub whead { bless \@_, "MyRow::WithHead" } table(1, { id => 't1', headings => [qw/NAME STRENGTH COMMENT/], rows => [ whead(qw/1 2 no/), data( qw/2 3 yes/), whead(qw/10 20 another/), data( qw/100 200 comment/), ], });
The next step is to go fully OO:
use strict; use warnings; { package MyElem; sub _line { shift; my $t = shift; print(("\t" x $t), @_, "\n"); } } { package MyCell; use base "MyElem"; sub output { my $self = shift; my ($tab) = @_; $self->_line($tab, qq(<td>@$self</td>)); } } { package MyCell::Header; use base "MyCell"; sub output { my $self = shift; my ($tab) = @_; $self->_line($tab, qq(<th>@$self</th>)); } } { package MyRow; use base "MyElem"; sub cells { my $self = shift; return map { bless [$_], "MyCell" } @$self; } sub output { my $self = shift; my ($tab) = @_; $self->_line($tab,qq(<tr>)); $_->output($tab + 1) for $self->cells; $self->_line($tab,qq(</tr>)); } } { package MyRow::WithHead; use base "MyRow"; sub cells { my $self = shift; my @cells = $self->SUPER::cells; bless $cells[0], "MyCell::Header"; return @cells; } } { package MyRow::AllHeads; use base "MyRow"; sub cells { my $self = shift; return map { bless [$_], "MyCell::Header" } @$self; } } { package MyTable; use base "MyElem"; sub output { my $self = shift; my ($tab) = @_; my @attributes; push @attributes, qq(id="$self->{id}") if $self->{id}; push @attributes, qq(class="$self->{class}") if $self->{class} +; push @attributes, qq(style="$self->{style}") if $self->{style} +; my $format = @attributes > 0 ? ' '.join(' ',@attributes) : ''; my $tag = 'table'.$format; $self->_line($tab, qq(<$tag>)); $self->_line($tab + 1,qq(<caption>$self->{caption}</caption>)) + if $self->{caption}; $self->{headings}->output($tab+1) if $self->{headings}; $_->output($tab+1) for @{$self->{rows} || []}; $self->_line($tab,q(</table>)); } } sub data { bless \@_, "MyRow" } sub whead { bless \@_, "MyRow::WithHead" } sub headings { bless \@_, "MyRow::AllHeads" } sub table { bless {@_}, "MyTable" } my $table = table( id => 't1', headings => headings(qw/NAME STRENGTH COMMENT/), rows => [ whead(qw/1 2 no/), data (qw/2 3 yes/), whead(qw/10 20 another/), data (qw/100 200 comment/), ], ); $table->output(1);
Each piece of data "knows" what it is (it's a "MyRow" or a "MyTable" or whatever). And because each class has an output method, each piece of data "knows" how to output itself.
You'll notice that MyTable itself does very little output of its own; instead delegating the job of output to each row. (And each row delegates the job of output to each cell.)
This means that if you need a particular special type of row, you can create a new subclass of MyRow, overriding the output method, then slip one of your spangly new rows into a table, and the table itself doesn't need to know any different. As far as the table is concerned, it's just another row.
There's still a few ways it could be improved; it's a bit inconsistent about where and when data gets blessed into the various classes. I don't have time for any further improvements right now, but I may come back to this post this evening.
Update: here's the final OO stage. The job of constructing the objects is moved to a new method within the classes. And the job of making sure the internals of each object are in order (e.g. making sure that the internals of a row objects are cell objects) is moved to _init.
Additionally, attribute processing and building start and end tags for an HTML element are defined in the MyElem class.
use strict; use warnings; { package MyElem; sub new { my $class = shift; my %opts = @_; my $self = bless \%opts, $class; $self->_init; return $self; } sub _init { my $self = shift; } sub _tagname { shift; return "span"; } sub id { shift->{id} } sub class { shift->{class} } sub style { shift->{style} } sub title { shift->{title} } sub _start_tag { my $self = shift; my $tag = join " ", $self->_tagname, map { sprintf '%s="%s"', $_, $self->$_ } grep { defined $self->$_ } qw(id class style title); return "<$tag>"; } sub _end_tag { my $self = shift; my $tag = $self->_tagname; return "</$tag>"; } sub output { my $self = shift; my ($tab) = @_; $self->_line($tab, $self->_start_tag, $self->value, $self->_en +d_tag); } sub _line { shift; my $t = shift; print(("\t" x $t), @_, "\n"); } } { package MyCell; use base "MyElem"; sub value { shift->{value}; } sub _tagname { return "td"; } sub output { my $self = shift; my ($tab) = @_; $self->_line($tab, $self->_start_tag, $self->value, $self->_en +d_tag); } } { package MyCell::Header; use base "MyCell"; sub _tagname { return "th"; } } { package MyRow; use base "MyElem"; use Scalar::Util qw(blessed); sub _init { my $self = shift; @{$self->{cells}} = map { blessed($_) ? $_ : "MyCell"->new(value => $_) } @{$self->{cells}||[]}; $self->SUPER::_init; } sub cells { @{shift->{cells}}; } sub _tagname { return "tr"; } sub output { my $self = shift; my ($tab) = @_; $self->_line($tab, $self->_start_tag); $_->output($tab + 1) for $self->cells; $self->_line($tab, $self->_end_tag); } } { package MyRow::WithHead; use base "MyRow"; use Scalar::Util qw(blessed); sub _init { my $self = shift; $self->{cells}[0] = "MyCell::Header"->new(value => $self->{cel +ls}[0]) unless blessed($self->{cells}[0]); $self->SUPER::_init; } } { package MyRow::AllHeads; use base "MyRow"; use Scalar::Util qw(blessed); sub _init { my $self = shift; @{$self->{cells}} = map { blessed($_) ? $_ : "MyCell::Header"->new(value => $_) } @{$self->{cells}||[]}; $self->SUPER::_init; } } { package MyTable; use base "MyElem"; use Scalar::Util qw(blessed); sub _init { my $self = shift; @{$self->{rows}} = map { blessed($_) ? $_ : "MyRow"->new(cells => $_) } @{$self->{rows}||[]}; $self->SUPER::_init; } sub rows { @{shift->{rows}}; } sub _tagname { return "table"; } sub output { my $self = shift; my ($tab) = @_; $self->_line($tab, $self->_start_tag); $self->_line($tab + 1,qq(<caption>$self->{caption}</caption>)) + if $self->{caption}; $_->output($tab+1) for $self->rows; $self->_line($tab, $self->_end_tag); } } sub data { my @args = @_; "MyRow"->new(cells => \@args) } sub whead { my @args = @_; "MyRow::WithHead"->new(cells => \@args) +} sub headings { my @args = @_; "MyRow::AllHeads"->new(cells => \@args) +} my $table = "MyTable"->new( id => 't1', rows => [ headings (qw/NAME STRENGTH COMMENT/), whead (qw/1 2 no/), data (qw/2 3 yes/), whead (qw/10 20 another/), data (qw/100 200 comment/), data (1000, "MyCell"->new(value => 2000, style=>"color:red +"), "again"), ], ); $table->output(1);
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re^2: I'm stuck adding more named parameters to a subroutine since the new named parameters might have the same name as the current ones.
by Lady_Aleena (Priest) on Mar 25, 2013 at 22:08 UTC | |
by tobyink (Canon) on Mar 26, 2013 at 09:39 UTC | |
by Lady_Aleena (Priest) on Mar 26, 2013 at 11:09 UTC |