File Coverage

blib/lib/HTML/Dashboard.pm
Criterion Covered Total %
statement 307 408 75.2
branch 69 92 75.0
condition 20 33 60.6
subroutine 46 77 59.7
pod 57 60 95.0
total 499 670 74.4


come all the _glue( \%tr ) . '>'; '; _glue( \%tr ) . '>' . $body . "\n"; ';
line stmt bran cond sub pod time code
1             package HTML::Dashboard;
2              
3 3     3   113690 use 5.008008;
  3         11  
  3         113  
4 3     3   18 use strict;
  3         4  
  3         97  
5 3     3   13 use warnings;
  3         12  
  3         97  
6              
7 3     3   14 use Carp;
  3         4  
  3         15645  
8              
9             our $VERSION = '0.03';
10              
11             # ============================================================
12              
13             # Test:
14             # - escape of \t and \n in as_text
15             # - DB access stuff (incl DB errors)
16              
17             # ------------------------------------------------------------
18              
19             my ( $HI, $MED, $LOW ) = ( 300, 200, 100 );
20              
21             sub new {
22 18     18 1 102 my $arg = shift;
23 18         28 my $class = $arg; # Always create through class name, not instance
24              
25             # Check _init_data_defaults below for meaning of individual variables...
26 18         178 my $self = bless { data => [],
27             first => undef,
28             last => undef,
29             records => undef,
30             pagesize => undef,
31             captions => [],
32             view => [],
33             sortrank => [],
34             comparator => undef,
35             format => {} }, $class;
36              
37             # If triggers are set, these will be stored in the following instance
38             # variables as multi-hash, keyed on column (==0 for row triggers) and
39             # priority. The value is an array-ref, containing the trigger sub and
40             # a key, which can be used to find the corresponding formatting options
41             # in $self->{opts}.
42             # Fired_col_triggers is set by _find_triggered_columns - since the
43             # format of the ENTIRE col may depend on the entry of a single cell
44             # in this col, col triggers must be evaluated ahead of time, and can
45             # not be applied row by row, as is possible for cell and row triggers.
46             # If a col trigger fired, its key will be stored in fired_col_triggers.
47 18         50 $self->{ ROW_TRIGGERS } = undef;
48 18         31 $self->{ COL_TRIGGERS } = undef;
49 18         32 $self->{ CELL_TRIGGERS } = undef;
50 18         25 $self->{ fired_col_triggers } = undef;
51              
52             # For each HTML tag (or Pseudo-tag, such as first_row), there is a hash,
53             # containing HTML/CSS formatting arguments. A hash is used, so that
54             # formatting commands from different (pseudo-)tags can be blended easily.
55             # For instance, if a command for has been set, as well as for
56             # even_row, we want to blend both of them together for even rows. Hashes
57             # can do this conveniently, using the following idiom:
58             # %tr = ( %tr, %{ $self->{opts}{even_row} } )
59             #
60             # The value of each $self->{opts}{...} is a hash-ref. Permissible keys
61             # into this hash-ref are "html" (or "color"), "class", and "style", eg
62             # $self->{opts}{table}{html} = "border='1'"
63             # For key "html", the value must be a string which can be inserted
64             # directly into the corresponding HTML tag.
65             # For key "color", the value must be a legal HTML color specification,
66             # ie. either in #rrggbb format or one of the recognized color names.
67             # For key "class", the value must be the name of a CSS class.
68             # For key "style", the value must be a string which can be given directly
69             # as value to a <... style="..."> argument of the corresponding HTML tag.
70             #
71             # The _glue() function is responsible for final formatting of these opts.
72 18         47 $self->{opts}{ table } = {};
73 18         37 $self->{opts}{ tr } = {};
74 18         32 $self->{opts}{ th } = {};
75 18         34 $self->{opts}{ td } = {};
76              
77 18         30 $self->{opts}{ first_row } = {};
78 18         32 $self->{opts}{ odd_row } = {};
79 18         34 $self->{opts}{ even_row } = {};
80 18         44 $self->{opts}{ last_row } = {};
81              
82 18         32 $self->{opts}{ first_col } = {};
83 18         34 $self->{opts}{ odd_col } = {};
84 18         30 $self->{opts}{ even_col } = {};
85 18         31 $self->{opts}{ last_col } = {};
86              
87 18         35 for my $i ( qw( ROW COL CELL ) ) {
88 54         72 for my $j ( $HI, $MED, $LOW ) {
89 162         450 $self->{opts}{ $i . "_TRIGGERS_" . $j } = {};
90             }
91             }
92              
93             # Defaults formats:
94 18         55 $self->_set_options( 'odd_row', '#eeeeee' );
95 18         38 $self->_set_options( 'table', 'border="1"' );
96              
97 18         47 return $self;
98             }
99              
100             # For debugging purposes...
101             sub dump_opts {
102 0     0 1 0 my $self = shift;
103              
104 0         0 my $out = '';
105              
106 0         0 $out .= "First: " . $self->{first} . "\n";
107 0         0 $out .= "Last: " . $self->{last} . "\n";
108 0         0 $out .= "Records: " . $self->{records} . "\n";
109 0         0 $out .= "Pagesize: " . $self->{pagesize} . "\n";
110 0         0 $out .= "Pagecount: " . $self->get_pagecount() . "\n";
111 0         0 $out .= "\n";
112 0         0 $out .= "Captions: " . join( ' | ', @{ $self->{captions} } ) . "\n";
  0         0  
113 0         0 $out .= "View: " . join( ', ', @{ $self->{view} } ) . "\n";
  0         0  
114              
115 0         0 for my $field ( keys %{ $self->{opts} } ) {
  0         0  
116 0         0 my @keys = keys %{ $self->{opts}{$field} };
  0         0  
117 0 0       0 if( scalar @keys == 0 ) { next; }
  0         0  
118              
119 0         0 $out .= "\n=== $field ===\n";
120 0         0 for my $key ( @keys ) {
121 0         0 $out .= " $key [ " . $self->{opts}{$field}{$key} . " ]\n";
122             }
123             }
124 0         0 return $out;
125             }
126              
127             # All of these instance variables can only be set once the data is known.
128             sub _init_data_defaults {
129 18     18   25 my ( $self, $first ) = @_;
130              
131             # Index of first data record:
132 18         25 $self->{first} = $first;
133              
134             # Index of last data record:
135 18         21 $self->{last} = scalar( @{ $self->{data} } ) - 1;
  18         40  
136              
137             # Number of records in data set (not counting captions)
138 18         35 $self->{records} = 1 + $self->{last} - $self->{first};
139              
140             # Number of records in page - defaults to all
141 18 50       45 unless( $self->{pagesize} ) {
142 18         31 $self->{pagesize} = $self->{records};
143             }
144              
145             # Column indices selected for view - defaults: all in natural order
146 18 50       18 unless( @{ $self->{view} } ) {
  18         39  
147 18         71 $self->{view} = [ 0..( scalar @{ $self->{data}[0] } - 1 ) ];
  18         67  
148             }
149              
150             # Row indices in sort order - default: natural order
151 18         85 $self->{sortrank} = [ $self->{first}..$self->{last} ];
152              
153              
154             # Further variables referred to in the constructor are:
155             # captions : an array, containing strings to be used as col captions
156             # comparator : a sub-ref, used for sorting (cf. set_sort() and _sort_rank() )
157             # format : a hash-ref, keyed on the column, which will receive the
158             # output of the formatter. The value is an array-ref, containing
159             # the formatter sub-ref, and an indicator whether the sub-ref
160             # is a formatter (single argument) or collater (row argument).
161             }
162              
163             # -----
164              
165             sub set_data_without_captions {
166 17     17 1 239 my ( $self, $data ) = @_;
167 17 50 33     123 unless( $data && ref( $data ) eq 'ARRAY' ) { carp "Not an array ref!" }
  0         0  
168              
169 17         31 $self->{data} = $data;
170              
171 17         43 $self->_init_data_defaults( 0 );
172             }
173              
174             sub set_data_with_captions {
175 1     1 1 13 my ( $self, $data ) = @_;
176 1 50 33     11 unless( $data && ref( $data ) eq 'ARRAY' ) { carp "Not an array ref" }
  0         0  
177              
178 1         3 $self->{data} = $data;
179              
180 1         5 $self->set_captions( @{ $data->[0] } );
  1         5  
181              
182 1         4 $self->_init_data_defaults( 1 );
183             }
184              
185             # -----
186              
187 0     0 1 0 sub get_query { my $self = shift; return $self->{sql} }
  0         0  
188              
189             sub set_query_without_captions {
190 0     0 1 0 my ( $self, $dbh, $sql ) = @_;
191 0         0 $self->{sql} = $sql;
192              
193 0         0 $self->{data} = $dbh->selectall_arrayref( $sql );
194              
195 0         0 $self->_init_data_defaults( 0 );
196             }
197              
198             sub set_query_with_captions {
199 0     0 1 0 my ( $self, $dbh, $sql ) = @_;
200 0         0 $self->{sql} = $sql;
201              
202 0         0 $self->{data} = $dbh->selectall_arrayref( $sql );
203              
204 0         0 $self->set_captions( @{ $self->{data}[0] } );
  0         0  
205              
206 0         0 $self->_init_data_defaults( 1 );
207             }
208              
209             # -----
210              
211             # UNIMPLEMENTED!
212             #
213             # The idea is to provide a way to set an SQL query, without executing it.
214             # Later, the query can be executed (using exec_query), while supplying
215             # values for bind variables.
216              
217 0     0 1 0 sub prepare_query_without_captions { }
218 0     0 1 0 sub prepare_query_with_captions { }
219 0     0 0 0 sub exec_query { } # Takes query parameters
220              
221             # -----
222              
223             sub set_sort {
224 2     2 1 20 my ( $self, $sub ) = @_;
225 2         7 $self->{comparator} = $sub;
226             }
227              
228             sub _sort_rank {
229 20     20   35 my $self = shift;
230              
231 20 100       47 unless( $self->{comparator} ) { return; }
  18         27  
232              
233 35         168 $self->{sortrank} =
234 2         12 [ sort { $self->{comparator}( $self->{data}[$a], $self->{data}[$b] ) }
235 2         5 @{ $self->{sortrank} } ];
236             }
237              
238             # -----
239              
240             # UNIMPLEMENTED!
241             #
242             # When there are subsequent rows, which have identical entries in some
243             # columns it can be neat to neat to suppress (leave blank) the repeated
244             # entries. These functions would take the col indices of the cols to be
245             # monitored for repeating behaviour and suppress them during output.
246              
247 0     0 0 0 sub set_skip_repeats { }
248 0     0 0 0 sub get_skip_repeats { }
249              
250             # ------------------------------------------------------------
251              
252 2     2 1 14 sub set_view { my ( $self, @cols ) = @_; $self->{view} = \@cols; }
  2         12  
253 0     0 1 0 sub get_view { my $self = shift; return $self->{view}; }
  0         0  
254              
255 3     3 1 16 sub set_captions { my ( $self, @caps ) = @_; $self->{captions} = \@caps; }
  3         8  
256 0     0 1 0 sub get_captions { my $self = shift; return $self->{captions}; }
  0         0  
257              
258             # Setting pagesize to undef, 0, or a negative value turns off pagination
259             sub set_pagesize {
260 1     1 1 6 my ( $self, $arg ) = @_;
261              
262 1 50 33     8 if( defined $arg && $arg > 0 ) {
263 1         3 $self->{pagesize} = $arg;
264             } else {
265 0         0 $self->{pagesize} = $self->{records};
266             }
267             }
268              
269 4     4 1 6 sub get_pagesize { my $self = shift; return $self->{pagesize}; }
  4         10  
270              
271             sub get_pagecount {
272 3     3 1 6 my $self = shift;
273              
274 3 50       11 if( !defined $self->{pagesize} ) { return 1; } # No pagination: 1 page
  0         0  
275              
276 3         8 my $full = int( $self->{records}/$self->{pagesize} ); # Count of full pages
277 3         6 my $frac = $self->{records} % $self->{pagesize}; # Records on last partial pg
278              
279 3 50       45 return $full + ( $frac ? 1 : 0 );
280             }
281              
282             # ------------------------------------------------------------
283              
284             # Takes a hash-ref - the slice of $self->{opts} for the selected HTML element
285             # and glues them together into a string which can be directly embedded
286             # into an HTML tag.
287             # In doing so, it makes sure that 'color' options are properly embedded
288             # into the 'style' argument, etc.
289              
290             sub _glue {
291 856     856   1043 my ( $self, $args ) = @_;
292              
293 856 100       1532 my $out = exists $args->{html} ? $args->{html} . ' ' : '';
294 856 50       1390 $out .= exists $args->{class} ? 'class="' . $args->{class} .'" ' : '';
295              
296 856 100 100     5627 if( exists $args->{color} && exists $args->{style} ) {
    100 100        
    100 66        
297 15         32 $out .= 'style="' . $args->{style} . '; ';
298 15         28 $out .= 'background-color: ' . $args->{color} . '" ';
299              
300             } elsif( !exists $args->{color} && exists $args->{style} ) {
301 7         15 $out .= 'style="' . $args->{style} . '" ';
302              
303             } elsif( exists $args->{color} && !exists $args->{style} ) {
304 107         224 $out .= 'style="background-color: ' . $args->{color} . ';" ';
305             }
306              
307 856         2241 return $out;
308             }
309              
310             # For pagination: finds the index of the first row to display and the
311             # index of the first row NOT to display, in other words, use the following
312             # loop to display: for( $i=$from; $i<$upto; $i++ )
313              
314             sub _find_range_for_page {
315 20     20   28 my ( $self, $page ) = @_;
316              
317             # Find the range of rows to plot:
318 20         36 my ( $from, $upto ) = ( 0, $self->{records} );
319              
320 20 100       36 if( defined $page ) {
321 2 50 33     11 if( $page < 0 || $page > $self->get_pagecount() ) {
    50          
322 0         0 carp "Out of bounds page $page requested - returning all rows.";
323              
324             } elsif( defined $self->{pagesize} ) { # only do pagination if switched on
325 2         7 $from = $page * $self->get_pagesize();
326 2         6 $upto = $from + $self->get_pagesize();
327              
328 2 100       8 if( $upto > $self->{records} ) {
329 1         4 $upto = $self->{records}; # Fractional last page
330             }
331             }
332             }
333              
334 20         42 return ( $from, $upto );
335             }
336              
337             sub _check_consistency {
338 20     20   26 my $self = shift;
339              
340 20         29 my ( $warn, $fatal ) = ( '', '' );
341              
342 20         23 my $colcnt = 0;
343 20 50       50 if( $self->{records} < 1 ) {
344 0         0 $warn .= "Empty data set\n";
345             } else {
346 20         23 my @colcount = map { scalar @{ $_ } } @{ $self->{data} };
  172         156  
  172         282  
  20         42  
347 20         28 $colcnt = $colcount[0];
348 20 50       26 if( scalar ( grep { $_ != $colcnt } @colcount ) ) {
  172         287  
349 0         0 $fatal .= "Data not rectangular. Column lengths: ";
350 0         0 $fatal .= join( ',', @colcount ) . "\n";
351             }
352             }
353              
354 20 50 66     22 if( @{ $self->{captions} } && $colcnt &&
  20   66     72  
  4         8  
355 4         18 scalar @{ $self->{captions} } != scalar @{ $self->{data}[0] } ) {
356 0         0 $fatal .= "Number of captions not equal to number of columns\n";
357             }
358              
359 20 50       30 if( scalar ( grep { $_ < 0 || $_ >= $colcnt } @{ $self->{view} } ) ) {
  93 50       337  
  20         40  
360 0         0 $fatal .= "Illegal index in view\n";
361             }
362              
363 20 50       76 if( $fatal ) { croak "$warn $fatal"; }
  0 50       0  
364 0         0 elsif( $warn ) { carp "$warn"; }
365             }
366              
367             # ------------------------------------------------------------
368             # Output Routines
369             #
370             # Tables are built up outside in (table->row->cell->contents).
371             # For each element, all opts are collected and all rules and
372             # triggers are evaluated. This leads to a fully formed HTML tag.
373             # Then the subsequent element (ie after
, etc)
374             # is evaluated in a similar fashion.
375              
376             # -----
377              
378             # Convention (for the following routines):
379             # $row, $col : the indices of the 'true' row or col in the full data set
380             # $prow, $vcol : the indices of the row in the current page or the column
381             # in the current view
382             # ------------------------------------------------------------
383              
384             sub as_text {
385 3     3 1 74 my ( $self, $page ) = @_;
386              
387 3         11 $self->_check_consistency();
388              
389 3         9 my ( $from, $upto ) = $self->_find_range_for_page( $page );
390              
391 3         9 $self->_sort_rank();
392              
393 3         4 my $body = '';
394 3 50       3 if( @{ $self->{captions} } ) {
  3         10  
395             # Array slice of @captions, indexed by @view...
396 0         0 $body .= join( "\t", @{ $self->{captions} }[ @{ $self->{view} } ] ) . "\n";
  0         0  
  0         0  
397             }
398              
399 3         7 my ( $prow, $vcol, $row, $col ) = ( 0, 0, 0, 0 );
400 3         8 foreach my $idx ( $from..$upto-1 ) {
401 19         29 $row = $self->{sortrank}[$idx];
402              
403 19         18 $vcol = 0;
404 19         19 foreach my $col ( @{ $self->{view} } ) {
  19         37  
405 91         164 my $token = $self->_content( $prow, $vcol, $row, $col );
406 91         182 $token =~ s/([\t\n\\])/\\$1/g; # Escape newline, tab, and backslash
407 91         141 $body .= "$token\t";
408 91         133 $vcol += 1;
409             }
410 19         29 chop $body; # remove the last tab, then replace with newline
411 19         22 $body .= "\n";
412              
413 19         22 $prow += 1;
414             }
415             # The last newline is NOT chopped - text ends with a newline.
416              
417 3         73 return $body;
418             }
419              
420             sub as_HTML {
421 17     17 1 65 my ( $self, $page ) = @_;
422              
423 17         39 $self->_check_consistency();
424              
425 17         48 my ( $from, $upto ) = $self->_find_range_for_page( $page );
426              
427 17         37 $self->_sort_rank();
428 17         54 $self->_find_triggered_columns();
429              
430              
431             # Find options - only simple options for table-tag:
432 17         23 my %table = %{ $self->{opts}{table} };
  17         74  
433              
434             # Build the body of the table:
435 17         28 my ( $prow, $body ) = ( 0, '' );
436 17         34 foreach my $idx ( $from..$upto-1 ) {
437 143         232 my $row = $self->{sortrank}[$idx];
438 143         301 $body .= $self->_row( $prow++, $row );
439             }
440              
441             # Build output:
442 17         28 my $out = "\n\n";
443 17         26 $out .= "\n";
444 17         33 $out .= '_glue( \%table ) . ">\n";
445 17         93 $out .= $self->_caption() . "\n";
446 17         57 $out .= $body . "\n";
447 17         19 $out .= "
\n\n";
448              
449 17         140 return $out;
450             }
451              
452             # Returns a fully formed caption row:
....
453             sub _caption {
454 17     17   29 my ( $self ) = @_;
455              
456 17 100       18 unless( @{ $self->{captions} } ) { return ''; }
  17         50  
  13         29  
457              
458 4         6 my %tr = %{ $self->{opts}{tr} };
  4         10  
459 4         5 my %th = %{ $self->{opts}{th} };
  4         8  
460 4         10 my $th = '_glue( \%th ) . '>';
461              
462 4         12 my $out = '
463 4         5 foreach my $col ( @{ $self->{view} } ) {
  4         9  
464 20         40 $out .= $th . $self->{captions}[$col] . '';
465             }
466 4         150 $out .= '
467 4         12 return $out . "\n";
468             }
469              
470             sub _row {
471 143     143   180 my ( $self, $prow, $row ) = @_;
472              
473             # Build options:
474 143         167 my %tr = %{ $self->{opts}{tr} };
  143         303  
475              
476             # Odd/Even Row
477 143 100       263 if( $prow %2 == 0 ) { %tr = ( %tr, %{ $self->{opts}{even_row} } ); }
  79         95  
  79         163  
478 64         80 else { %tr = ( %tr, %{ $self->{opts}{odd_row} } ); }
  64         178  
479              
480             # First/Last Row
481 143 100       379 if( $prow == 0 ) {
    100          
482 17         24 %tr = ( %tr, %{ $self->{opts}{first_row} } ); # First row
  17         40  
483             } elsif( $prow == $self->{pagesize} - 1 ) {
484 16         22 %tr = ( %tr, %{ $self->{opts}{last_row} } ); # Last row
  16         46  
485             }
486              
487             # Triggers: Hi->Med->Low
488 143 50       288 if( exists $self->{ROW_TRIGGERS} ) {
489 143         159 for my $prio ( sort { $b <=> $a } keys %{ $self->{ROW_TRIGGERS}{0} } ) {
  9         24  
  143         415  
490 34         73 my ( $trig, $key ) = @{ $self->{ROW_TRIGGERS}{0}{$prio} };
  34         74  
491              
492 34 100       146 if( $trig->( $self->{data}[$row], $prow, $row ) ) {
493 15         73 %tr = ( %tr, %{ $self->{opts}{$key} } );
  15         54  
494 15         32 last;
495             }
496             }
497             }
498              
499             # Build column body:
500 143         309 my ( $vcol, $body ) = ( 0, '' );
501 143         157 foreach my $col ( @{ $self->{view} } ) {
  143         267  
502 688         1820 $body .= $self->_cell( $prow, $vcol++, $row, $col );
503             }
504              
505 143         317 my $out = '
506              
507 143         488 return $out;
508             }
509              
510             sub _cell {
511 688     688   934 my ( $self, $prow, $vcol, $row, $col ) = @_;
512              
513 688         692 my %td = %{ $self->{opts}{td} };
  688         1470  
514              
515              
516             # Even/odd column
517 688 100       1471 if( $vcol %2 == 0 ) { %td = ( %td, %{ $self->{opts}{even_col} } ); }
  411         526  
  411         846  
518 277         373 else { %td = ( %td, %{ $self->{opts}{odd_col} } ); }
  277         538  
519              
520             # First/last column
521 688 100       1081 if( $vcol == 0 ) {
  545 100       1221  
522 143         203 %td = ( %td, %{ $self->{opts}{first_col} } ); # First column
  143         269  
523             } elsif( $vcol == scalar( @{ $self->{view} } )-1 ) {
524 143         153 %td = ( %td, %{ $self->{opts}{last_col} } ); # Last column
  143         269  
525             }
526              
527              
528             # Cell triggers
529 688         920 my $flag = 0;
530 688 100       1381 if( exists $self->{CELL_TRIGGERS}{ $col } ) {
531 27         35 for my $prio ( sort { $b <=> $a } keys %{ $self->{CELL_TRIGGERS}{$col} } ){
  27         43  
  27         81  
532 39         93 my ( $trig, $key ) = @{ $self->{CELL_TRIGGERS}{ $col }{ $prio } };
  39         80  
533              
534 39 100       102 if( $trig->( $self->{data}[$row][$col], $vcol, $col ) ) {
535 11         63 %td = ( %td, %{ $self->{opts}{$key} } );
  11         34  
536 11         15 $flag = 1;
537 11         19 last;
538             }
539             }
540             }
541              
542              
543             # Col triggers - only if no cell triggers have fired!
544 688 100 100     2697 if( !$flag && exists $self->{fired_col_triggers}{ $col } ) {
545 17         32 my $key = $self->{fired_col_triggers}{ $col };
546 17         22 %td = ( %td, %{ $self->{opts}{ $key } } );
  17         56  
547             }
548              
549              
550 688         1440 return join '', '_glue( \%td ), '>',
551             $self->_content( $prow, $vcol, $row, $col ), '
552             }
553              
554             sub _content {
555 779     779   1134 my ( $self, $prow, $vcol, $row, $col ) = @_;
556              
557 779 100       1569 if( exists $self->{format}{$col} ) {
558 18         19 my ( $format, $type ) = @{ $self->{format}{$col} };
  18         36  
559              
560 18 100       35 if( $type eq 'format' ) {
561 9         26 return $format->( $self->{data}[$row][$col] );
562             } else {
563 9         9 return $format->( @{ $self->{data}[$row] } );
  9         27  
564             }
565             }
566              
567 761         3563 return $self->{data}[$row][$col];
568             }
569              
570             # ------------------------------------------------------------
571              
572             sub _get_options {
573 0     0   0 my ( $self, $opt ) = @_; # $opt must be: first_col, or odd_row, etc
574              
575             # Worry about notfound?
576              
577             # Return value is always: undef or a hash-ref: key/value (?)
578              
579 0         0 return $self->{opts}{$opt};
580             }
581              
582             sub _set_options {
583 54     54   85 my ( $self, $opt, $arg, $val ) = @_;
584              
585             # Decide if one or two arguments are present...
586 54 100 33     123 if( !defined $val ) { # One argument - figure out the 'key', arg is 'val'
    50          
587 49         162 my %html = ( table => 1, tr => 1, th => 1, td => 1 );
588 49 100       93 my $field = exists $html{$opt} ? 'html' : 'color';
589              
590 49         194 $self->{opts}{$opt}{$field} = $arg;
591              
592             } elsif( $arg eq 'class' || $arg eq 'style' ) { # Two args: key and val
593 5         19 $self->{opts}{$opt}{$arg} = $val;
594              
595             } else {
596 0         0 carp "Illegal arguments - ,$opt, ,$arg, ,$val,";
597             }
598             }
599              
600             # ------------------------------------------------------------
601             # Arguments:
602             # $htmlargs || class => $class || style => $style
603              
604 1     1 1 4 sub set_table { my $s = shift; return $s->_set_options( 'table', @_ ); }
  1         3  
605 0     0 1 0 sub set_tr { my $s = shift; return $s->_set_options( 'tr', @_ ); }
  0         0  
606 0     0 1 0 sub set_th { my $s = shift; return $s->_set_options( 'th', @_ ); }
  0         0  
607 0     0 1 0 sub set_td { my $s = shift; return $s->_set_options( 'td', @_ ); }
  0         0  
608              
609 0     0 1 0 sub get_table { my $s = shift; return $s->_get_options( 'table' ); }
  0         0  
610 0     0 1 0 sub get_tr { my $s = shift; return $s->_get_options( 'tr' ); }
  0         0  
611 0     0 1 0 sub get_th { my $s = shift; return $s->_get_options( 'th' ); }
  0         0  
612 0     0 1 0 sub get_td { my $s = shift; return $s->_get_options( 'td' ); }
  0         0  
613              
614             # ------------------------------------------------------------
615             # Arguments:
616             # $color || class => $class || style => $style
617              
618 0     0 1 0 sub get_first_row { my $s = shift; return $s->_get_options( 'first_row' ); }
  0         0  
619 0     0 1 0 sub get_odd_row { my $s = shift; return $s->_get_options( 'odd_row' ); }
  0         0  
620 0     0 1 0 sub get_even_row { my $s = shift; return $s->_get_options( 'even_row' ); }
  0         0  
621 0     0 1 0 sub get_last_row { my $s = shift; return $s->_get_options( 'last_row' ); }
  0         0  
622              
623 0     0 1 0 sub get_first_col { my $s = shift; return $s->_get_options( 'first_col' ); }
  0         0  
624 0     0 1 0 sub get_odd_col { my $s = shift; return $s->_get_options( 'odd_col' ); }
  0         0  
625 0     0 1 0 sub get_even_col { my $s = shift; return $s->_get_options( 'even_col' ); }
  0         0  
626 0     0 1 0 sub get_last_col { my $s = shift; return $s->_get_options( 'last_col' ); }
  0         0  
627              
628             # -----
629              
630 1     1 1 5 sub set_first_row { my $s = shift; $s->_set_options( 'first_row', @_ ); }
  1         3  
631 1     1 1 5 sub set_odd_row { my $s = shift; $s->_set_options( 'odd_row', @_ ); }
  1         3  
632 1     1 1 6 sub set_even_row { my $s = shift; $s->_set_options( 'even_row', @_ ); }
  1         4  
633 1     1 1 6 sub set_last_row { my $s = shift; $s->_set_options( 'last_row', @_ ); }
  1         4  
634              
635 0     0 1 0 sub set_first_col { my $s = shift; $s->_set_options( 'first_col', @_ ); }
  0         0  
636 0     0 1 0 sub set_odd_col { my $s = shift; $s->_set_options( 'odd_col', @_ ); }
  0         0  
637 1     1 1 6 sub set_even_col { my $s = shift; $s->_set_options( 'even_col', @_ ); }
  1         3  
638 1     1 1 5 sub set_last_col { my $s = shift; $s->_set_options( 'last_col', @_ ); }
  1         2  
639              
640             # ------------------------------------------------------------
641             # Arguments:
642             # $col, $sub, $color || class => $class || style => $style
643              
644             sub _set_trigger {
645 11     11   25 my ( $self, $elem, $prio, $col, $sub, @args ) = @_;
646              
647 11         19 my $trig = join '_', $elem, 'TRIGGERS';
648 11         20 my $key = join '_', $trig, $prio;
649              
650 11         35 $self->{ $trig }{ $col }{ $prio } = [ $sub, $key ];
651 11         23 $self->_set_options( $key, @args );
652             }
653              
654             # sub set_row_trigger { my ( $self, $prio, $sub, @args ) = @_; }
655             sub set_row_trigger {
656 4     4 1 8 my ( $self, $prio, @args ) = @_;
657 4         18 $self->_set_trigger( 'ROW', $prio, 0, @args );
658             }
659 2     2 1 14 sub set_row_hi { my $self = shift; $self->set_row_trigger( $HI, @_ ); }
  2         7  
660 0     0 1 0 sub set_row_med { my $self = shift; $self->set_row_trigger( $MED, @_ ); }
  0         0  
661 2     2 1 15 sub set_row_low { my $self = shift; $self->set_row_trigger( $LOW, @_ ); }
  2         7  
662              
663             # sub set_col_trigger { my ( $self, $prio, $col, $sub, @args ) = @_; }
664 2     2 1 3 sub set_col_trigger { my $self = shift; $self->_set_trigger( 'COL', @_); }
  2         6  
665 0     0 1 0 sub set_col_hi { my $self = shift; $self->set_col_trigger( $HI, @_ ); }
  0         0  
666 1     1 1 7 sub set_col_med { my $self = shift; $self->set_col_trigger( $MED, @_ ); }
  1         9  
667 1     1 1 10 sub set_col_low { my $self = shift; $self->set_col_trigger( $LOW, @_ ); }
  1         3  
668              
669             # sub set_cell_trigger { my ( $self, $prio, $col, $sub, @args ) = @_; }
670 5     5 1 5 sub set_cell_trigger { my $self = shift; $self->_set_trigger( 'CELL', @_); }
  5         45  
671 3     3 1 19 sub set_cell_hi { my $self = shift; $self->set_cell_trigger( $HI, @_ ); }
  3         8  
672 1     1 1 8 sub set_cell_med { my $self = shift; $self->set_cell_trigger( $MED, @_ ); }
  1         3  
673 1     1 1 8 sub set_cell_low { my $self = shift; $self->set_cell_trigger( $LOW, @_ ); }
  1         5  
674              
675              
676             sub _find_triggered_columns {
677 17     17   20 my $self = shift;
678              
679 17 50       45 unless( exists $self->{COL_TRIGGERS} ) { return; }
  0         0  
680              
681 17         22 my $vcol = -1;
682 17         18 for my $col ( @{ $self->{view} } ) {
  17         35  
683 82         88 $vcol += 1;
684              
685 82 100       189 unless( exists $self->{COL_TRIGGERS}{ $col } ) { next; }
  80         107  
686              
687             # Low->Med->Hi : higher priority clobbers earlier results!
688 2         4 for my $prio ( sort { $a <=> $b } keys %{ $self->{COL_TRIGGERS}{$col} } ) {
  0         0  
  2         8  
689 2         2 my ( $trig, $key ) = @{ $self->{COL_TRIGGERS}{$col}{$prio} };
  2         7  
690              
691 2         7 foreach my $idx ( 0..$self->{records} ) {
692 10         45 my $row = $self->{sortrank}[$idx];
693 10 100       34 if( $trig->( $self->{data}[$row][$col], $vcol, $col ) ) {
694 2         12 $self->{fired_col_triggers}{$col} = $key;
695 2         7 last;
696             }
697             }
698             }
699             }
700             }
701              
702             # ------------------------------------------------------------
703             # Arguments:
704             # $col, $sub
705              
706             # For each column, a formatting function can be set. The function is called
707             # for each cell in the column. The return value of the function is used as
708             # printable content of the cell.
709             # A 'format' function receives as argument the raw value of the cell.
710             # A 'collate' function receives as argument the entire row as an array.
711             #
712             # Examples:
713             # format:
714             # sub { substr $_[0], 0, 3 } prints only first three chars of cell val
715             # collate:
716             # sub { $_[0] . '=' . $_[1] } concates first and second column value
717              
718             sub set_format {
719 1     1 1 9 my ( $self, $col, $sub ) = @_;
720 1         5 $self->{format}{$col} = [ $sub, 'format' ];
721             }
722              
723             sub set_collate {
724 1     1 1 8 my ( $self, $col, $sub ) = @_;
725 1         5 $self->{format}{$col} = [ $sub, 'collate' ];
726             }
727              
728             1;
729              
730             __END__