File Coverage

blib/lib/HTML/ReportWriter/PagingAndSorting.pm
Criterion Covered Total %
statement 12 193 6.2
branch 0 124 0.0
condition 0 71 0.0
subroutine 4 19 21.0
pod 12 12 100.0
total 28 419 6.6


'; '; ';
line stmt bran cond sub pod time code
1             package HTML::ReportWriter::PagingAndSorting;
2              
3 1     1   136314 use strict;
  1         3  
  1         38  
4 1     1   917 use POSIX;
  1         9647  
  1         8  
5 1     1   998030 use CGI;
  1         24010  
  1         8  
6 1     1   1011 use List::MoreUtils qw(none firstidx);
  1         1389  
  1         3203  
7              
8             our $VERSION = '1.5.0';
9              
10             =head1 NAME
11              
12             HTML::ReportWriter::PagingAndSorting - Contains logic for paging/sorting function of ReportWriter
13              
14             =head1 SYNOPSIS
15              
16             Example script:
17              
18             #!/usr/bin/perl -w
19              
20             use strict;
21             use HTML::ReportWriter::PagingAndSorting;
22             use CGI;
23             use Template;
24             use DBI;
25              
26             my $template = Template->new( { INCLUDE_PATH => '/templates' } );
27             my $co = new CGI;
28             my $paging = HTML::ReportWriter::PagingAndSorting->new({
29             CGI_OBJECT => $co,
30             DEFAULT_SORT => 'date',
31             SORTABLE_COLUMNS => [
32             {
33             get => 'name',
34             sql => 'people.name',
35             display => 'Full Name',
36             sortable => 0,
37             },
38             {
39             get => 'age',
40             sql => 'people.age',
41             display => 'Age (in years)',
42             sortable => 1,
43             },
44             ],
45             });
46              
47             my $dbh = DBI->connect('DBI:mysql:foo', 'bar', 'baz');
48              
49             my $sql = "SELECT SQL_CALC_FOUND_ROWS id, name, age FROM people";
50              
51             my $sort = $paging->get_mysql_sort();
52             my $limit = $paging->get_mysql_limit();
53              
54             my $sth = $dbh->prepare("$sql $sort $limit");
55             $sth->execute();
56             my ($count) = $dbh->selectrow_array('SELECT FOUND_ROWS() AS num');
57              
58             $paging->num_results($count);
59              
60             while(my $href = $sth->fetchrow_hashref)
61             {
62             push @{$vars{'results'}}, $href;
63             }
64             $vars{'sorting'} = $paging->get_sortable_table_header();
65             $vars{'paging'} = $paging->get_paging_table();
66              
67             print $co->header;
68             $template->process('display.html', \%vars);
69              
70             Example template (display.html in the above example):
71              
72            
73            
74            
75             Simple Report
76            
77            
78            
79             [% rowcounter = 1 %]
80            
81            
82            
83            
84             [% sorting %]
85             [%- FOREACH x = results %]
86             [%- IF rowcounter mod 2 %]
87             [%- rowclass = "table_odd" %]
88             [%- ELSE %]
89             [%- rowclass = "table_even" %]
90             [%- END %]
91            
92             [% x.name %][% x.age %]
93            
94             [%- rowcounter = rowcounter + 1 %]
95             [% END %]
96            
97            
98            
99            
100            
101             [% paging %]
102            
103            
104            
105            
106            
107            

108            
109            
110              
111             The DB is left to the reader's imagination.
112              
113             =head1 DESCRIPTION
114              
115             Generates HTML table headers and footers to handle sorting and paging for tabular reports.
116             Additionally, generates SQL fragments to insert into queries to handle paging and sorting.
117              
118             =head1 METHODS
119              
120             =over
121              
122             =item B
123              
124             Creates and returns a new paging object. new() accepts a single hashref as an argument, options.
125             Options may be one or more of the following:
126              
127             =over
128              
129             =item CGI_OBJECT:
130              
131             A previously-created CGI object. Saves the overhead of loading a new one.
132              
133             =item RESULTS_PER_PAGE:
134              
135             The number of rows to be displayed per page. Setting this number to 0 will disable paging, but
136             only if you call the get_limit() method. If you directly call get_DRIVER_limit, it is not
137             guaranteed to work. default: 25
138              
139             =item PAGES_IN_LIST:
140              
141             The number of pages that will appear in the paging array. default: 5
142             Note: this number must be odd. If it is even, it will be decremented by 1.
143              
144             =item PAGE_VARIABLE:
145              
146             The GET parameter that defines which page of the results we are viewing. default: page
147              
148             =item NUM_RESULTS:
149              
150             This should not be used when instantiating the object, because it means that in doing so
151             you have bypassed the get_mysql_limit function, which is against the design of this object.
152              
153             =item SORT_VARIABLE:
154              
155             The GET parameter that defines which column is being used for sorting and the direction of the
156             sort. default: sort
157              
158             =item DEFAULT_SORT:
159              
160             Which column should be sorted by when the page is drawn for the first time. User the version
161             of the name that is provided as the C param in the C definition. Sort
162             direction is ascending by default. No column is sorted on by default. Examples:
163              
164             DEFAULT_SORT => 'date',
165             DEFAULT_SORT => 'date,ASC',
166             DEFAULT_SORT => 'date,DESC',
167              
168             =item CURRENT_PAGE:
169              
170             Which page are we currently viewing? Should never need to be overridden.
171             default: $cgi->param(PAGE_VARIABLE)
172              
173             =item CURRENT_SORT_COL:
174              
175             Which column are we currently sorting by? Should never need to be overridden.
176              
177             =item CURRENT_SORT_DIR:
178              
179             Which direction are we currently sorting? Should never need to be overridden.
180              
181             =item LANGUAGE_TOKENS:
182              
183             This is a hashref used to make this module multilingual. Currently, there is only one language token:
184             display_token. It is, by default, 'Displaying Results $1 to $2 of $3' where $1, $2, and $3 are replaced
185             with the appropriate values.
186              
187             =back
188              
189             The following options control formatting, and should be self-explanatory. Their defaults
190             are listed next to the item.
191              
192             =over
193              
194             =item PREV_HTML <
195              
196             =item NEXT_HTML >
197              
198             =item FIRST_HTML «
199              
200             =item LAST_HTML »
201              
202             =item ASC_HTML ↑
203              
204             =item DESC_HTML ↓
205              
206             =back
207              
208             Finally, it accepts a list of sortable columns. A column consists of the following properties:
209              
210             =over
211              
212             =item get:
213              
214             The name of the column on the get string. This is used in conjunction with SORT_VARIABLE as in:
215             C<< http://example.com/cgi-bin/reports/foo.cgi?SORT_VARIABLE=SORTABLE_COLUMNS->[0]->{'get'} >>
216              
217             =item sql:
218              
219             The name of the column in sql. Include any necessary table aliases in this definition.
220             Example: C<< sql => 'tablename.columnname' >>
221              
222             =item display:
223              
224             The name of the column in the display. Used by get_sortable_html_table_header
225              
226             =item sortable:
227              
228             True/False (1/0). Defines the behaviour of the column. Does it allow sorting?
229              
230             =item order:
231              
232             Optional property that defines the sql used in the order clause. If not set, the 'sql' property is used.
233             See the ReportWriter documentation for more information on this property.
234              
235             =back
236              
237             Each sortable column definition is a hashref, and SORTABLE_COLUMNS itself is an arrayref containing
238             one or more of these hashrefs. Example:
239              
240             SORTABLE_COLUMNS => [
241             {
242             'get' => 'name',
243             'sql' => 'person.name',
244             'display' => 'Name',
245             'sortable' => 0,
246             },
247             {
248             'get' => 'age',
249             'sql' => 'person.age',
250             'display' => 'Age (in years)',
251             'sortable' => 1,
252             },
253             ]
254              
255             =cut
256              
257             sub new
258             {
259 0     0 1   my ($pkg, $args) = @_;
260              
261 0 0 0       if(!defined($args->{'CGI_OBJECT'}) || !UNIVERSAL::isa($args->{'CGI_OBJECT'}, "CGI"))
262             {
263 0           $args->{'CGI_OBJECT'} = new CGI;
264 0           warn "Creating new CGI object";
265             }
266              
267             # paging setup
268 0 0         defined $args->{'RESULTS_PER_PAGE'} or $args->{'RESULTS_PER_PAGE'} = 25;
269 0 0         defined $args->{'PAGES_IN_LIST'} or $args->{'PAGES_IN_LIST'} = 5;
270 0 0         defined $args->{'PAGE_VARIABLE'} or $args->{'PAGE_VARIABLE'} = 'page';
271 0 0         defined $args->{'NUM_RESULTS'} or $args->{'NUM_RESULTS'} = 0;
272              
273             # sorting setup
274 0 0         defined $args->{'SORT_VARIABLE'} or $args->{'SORT_VARIABLE'} = 'sort';
275 0 0         defined $args->{'DEFAULT_SORT'} or $args->{'DEFAULT_SORT'} = '';
276              
277             # current page handling
278 0   0       my $page = $args->{'CGI_OBJECT'}->param($args->{'PAGE_VARIABLE'}) || 1;
279 0 0         defined $args->{'CURRENT_PAGE'} or $args->{'CURRENT_PAGE'} = $page;
280              
281             # current sort handling
282 0   0       my $sort = $args->{'CGI_OBJECT'}->param($args->{'SORT_VARIABLE'}) || $args->{'DEFAULT_SORT'};
283 0           my ($sort_col, $sort_dir);
284 0 0         if($sort)
285             {
286 0           ($sort_col, $sort_dir) = split /,/, $sort;
287             }
288              
289 0 0 0       if(!$sort_dir && $sort_col)
290             {
291             # defaults to ascending order
292 0           $sort_dir = 'ASC';
293             }
294 0 0         defined $args->{'CURRENT_SORT_COL'} or $args->{'CURRENT_SORT_COL'} = $sort_col;
295 0 0         defined $args->{'CURRENT_SORT_DIR'} or $args->{'CURRENT_SORT_DIR'} = $sort_dir;
296              
297             # options to control HTML output - Paging
298 0 0         defined $args->{'PREV_HTML'} or $args->{'PREV_HTML'} = '<';
299 0 0         defined $args->{'NEXT_HTML'} or $args->{'NEXT_HTML'} = '>';
300 0 0         defined $args->{'FIRST_HTML'} or $args->{'FIRST_HTML'} = '«';
301 0 0         defined $args->{'LAST_HTML'} or $args->{'LAST_HTML'} = '»';
302              
303             # options to control HTML output - Sorting
304 0 0         defined $args->{'ASC_HTML'} or $args->{'ASC_HTML'} = ' ↑';
305 0 0         defined $args->{'DESC_HTML'} or $args->{'DESC_HTML'} = ' ↓';
306              
307             # round down PAGES_IN_LIST if it isn't odd
308 0 0         if(!$args->{'PAGES_IN_LIST'} % 2)
309             {
310 0           $args->{'PAGES_IN_LIST'} -= 1;
311 0           warn "PAGES_IN_LIST must be odd. See the documentation (if it exists) for the reason why.";
312             }
313              
314             #Provide a way to replace the english text
315 0 0         defined $args->{'LANGUAGE_TOKENS'} or $args->{'LANGUAGE_TOKENS'} = { display_token => 'Displaying Results $1 to $2 of $3' };
316              
317             # don't die here because they may not want to use the sorting, but may have a GET/POST var whose name is the
318             # same as $args->{'SORT_VARIABLE'}. We'll die if they call a sort function.
319 0 0 0       if(($sort_dir || $sort_col) && !$args->{'SORTABLE_COLUMNS'} || ref($args->{'SORTABLE_COLUMNS'}) ne 'ARRAY')
      0        
      0        
320             {
321 0           warn "SORTABLE_COLUMNS Is either not defined or not an arrayref, sorting disabled";
322             }
323              
324 0           my $self = bless $args, $pkg;
325              
326 0           return $self;
327             }
328              
329             =item B
330              
331             Accepts the number of results that will be generated for the query being used. Sets the number of
332             rows internally to the number supplied, and returns that number as confirmation of the setting.
333              
334             If the CURRENT_PAGE has been initialized to something greater than the total number of available pages
335             (where $total_pages = ceil(NUM_RESULTS / RESULTS_PER_PAGE)), then the function will reset CURRENT_PAGE to
336             the last available page and return FALSE. This should signal the calling script to regenerate the result set
337             if the result set has already been generated (as would be the case if using the SQL_CALC_FOUND_ROWS feature
338             of MySQL 4.0+).
339              
340             Without a function like C, this number could be generated by an additional SQL query that
341             counts the total number of rows, using the same WHERE clause:
342              
343             my $count = $dbh->selectrow_array("SELECT count(*) FROM articles WHERE ...");
344             $paging->num_results($count);
345              
346             =cut
347              
348             sub num_results
349             {
350 0     0 1   my ($self, $num) = @_;
351              
352 0           $self->{'NUM_RESULTS'} = $num;
353 0           my $to_return = $num;
354              
355 0 0         if($self->{'RESULTS_PER_PAGE'} != 0)
356             {
357 0           my $total_pages = ceil($self->{'NUM_RESULTS'} / $self->{'RESULTS_PER_PAGE'});
358              
359             # if somehow we paged past the end of the results, get us back on track
360 0 0         if($self->{'CURRENT_PAGE'} > $total_pages)
361             {
362 0           $self->{'CURRENT_PAGE'} = $total_pages;
363 0           $to_return = 0;
364             }
365             }
366             else
367             {
368             # if paging is disabled (RPP == 0) then we can never go past the
369             # first page
370 0 0         if($self->{'CURRENT_PAGE'} > 1)
371             {
372 0           $self->{'CURRENT_PAGE'} = 1;
373 0           $to_return = 0;
374             }
375             }
376              
377 0           return $to_return;
378             }
379              
380             =item B
381              
382             @pages = $pager->generate_paging_array();
383              
384             Determines what page the viewer is currently on, and generates an array representing which
385             pages are previous and next, first and last. Returns that array.
386              
387             =cut
388              
389             sub generate_paging_array
390             {
391 0     0 1   my $self = shift;
392              
393             # not the cleanest way of doing this -- short-circuit out of this function
394             # if paging is disabled with RPP == 0
395 0 0         if($self->{'RESULTS_PER_PAGE'} == 0)
396             {
397 0           return;
398             }
399              
400 0           my $pages_on_either_side = ceil(($self->{'PAGES_IN_LIST'} - 1) / 2);
401 0           my $total_pages = ceil($self->{'NUM_RESULTS'} / $self->{'RESULTS_PER_PAGE'});
402              
403             # if somehow we paged past the end of the results, get us back on track
404 0 0         if($self->{'CURRENT_PAGE'} > $total_pages)
405             {
406 0           $self->{'CURRENT_PAGE'} = $total_pages;
407             }
408              
409 0           my @pages = ();
410              
411             # at the end of the results
412 0 0 0       if($self->{'CURRENT_PAGE'} == $total_pages)
    0          
    0          
    0          
413             {
414 0           my $min = $self->{'CURRENT_PAGE'} - $self->{'PAGES_IN_LIST'};
415 0 0         $min = 1 if $min < 1;
416              
417 0           push @pages, $min..$self->{'CURRENT_PAGE'};
418             }
419             # just right
420             elsif(($self->{'CURRENT_PAGE'} - $pages_on_either_side) >= 1 &&
421             ($self->{'CURRENT_PAGE'} + $pages_on_either_side) <= $total_pages)
422             {
423 0           my $min = $self->{'CURRENT_PAGE'} - $pages_on_either_side;
424 0           my $max = $self->{'CURRENT_PAGE'} + $pages_on_either_side;
425              
426 0           push @pages, $min..$max;
427             }
428             # too close to the beginning
429             elsif($self->{'CURRENT_PAGE'} - $self->{'PAGES_IN_LIST'} < 1)
430             {
431 0           my $min = 1;
432 0 0         if($self->{'PAGES_IN_LIST'} > $total_pages)
433             {
434 0           push @pages, $min..$total_pages;
435             }
436             else
437             {
438 0           push @pages, $min..$self->{'PAGES_IN_LIST'};
439             }
440             }
441             # too close to the end
442             elsif($self->{'CURRENT_PAGE'} + $self->{'PAGES_IN_LIST'} > $total_pages)
443             {
444 0           my $min = $self->{'CURRENT_PAGE'} - ($self->{'PAGES_IN_LIST'} - ($total_pages - $self->{'CURRENT_PAGE'}));
445 0 0         $min = 1 if $min < 1;
446              
447 0           push @pages, $min..$total_pages;
448             }
449             else
450             {
451 0           die "This code should never execute";
452             }
453              
454 0           return (@pages);
455             }
456              
457             =item B
458              
459             $url = $pager->get_page_link($page_number);
460              
461             Saves the existing sort and page settings, and then uses some CGI module magic to generate
462             a URL saving all parameters that were passed in except the page number, which is set to the
463             requested page. Used to generate paging html.
464              
465             =cut
466              
467             sub get_page_link
468             {
469 0     0 1   my ($self, $page) = @_;
470              
471             # save the old page number and sort (this is necessary since we have a shared CGI object)
472 0           my $oldpage = $self->{'CGI_OBJECT'}->param($self->{'PAGE_VARIABLE'});
473              
474             # generate a url with the new page number
475 0           $self->{'CGI_OBJECT'}->param($self->{'PAGE_VARIABLE'}, $page);
476 0           my $url = $self->{'CGI_OBJECT'}->url( -absolute => 1, -query => 1, -path_info => 1 );
477              
478             # restore the old page number
479 0 0         if($oldpage)
480             {
481 0           $self->{'CGI_OBJECT'}->param($self->{'PAGE_VARIABLE'}, $oldpage);
482             }
483             else
484             {
485 0           $self->{'CGI_OBJECT'}->delete($self->{'PAGE_VARIABLE'});
486             }
487              
488 0           return $url;
489             }
490              
491             =item B
492              
493             Gets the paging array, generates links for each part of that array, and then generates HTML for
494             the paging block based on the display settings that were configured during instantiation.
495              
496             =cut
497              
498             sub get_paging_table
499             {
500 0     0 1   my ($self) = @_;
501              
502             # if we have no results, or paging is disabled (RPP == 0), exit
503 0 0 0       return '' if $self->{'NUM_RESULTS'} == 0 || $self->{'RESULTS_PER_PAGE'} == 0;
504              
505 0           my @paging_array = $self->generate_paging_array();
506              
507 0           my $total_pages = ceil($self->{'NUM_RESULTS'} / $self->{'RESULTS_PER_PAGE'});
508 0           my $string = '';
509              
510 0 0         my $first = ($self->{'CURRENT_PAGE'} == 1 ? 1 : (($self->{'CURRENT_PAGE'} - 1) * $self->{'RESULTS_PER_PAGE'}));
511 0 0         my $last = ($self->{'CURRENT_PAGE'} == $total_pages ? $self->{'NUM_RESULTS'} : ($self->{'CURRENT_PAGE'} * $self->{'RESULTS_PER_PAGE'}));
512 0           my $total = $self->{'NUM_RESULTS'};
513              
514 0           my $language = $self->{'LANGUAGE_TOKENS'}->{'display_token'};
515 0           $language =~ s/\$1/$first/g;
516 0           $language =~ s/\$2/$last/g;
517 0           $language =~ s/\$3/$total/g;
518            
519             # paging header
520 0           $string = ''; '; ';
521 0           $string .= ''. $language . '
522              
523             # process the elements in order
524 0           foreach ('FIRST','PREV',@paging_array,'NEXT','LAST')
525             {
526 0           $string .= '';
527              
528 0 0 0       if(($_ eq 'FIRST' || $_ eq 'PREV') && $self->{'CURRENT_PAGE'} != 1)
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
529             {
530 0 0         my $url = $self->get_page_link(($_ eq 'FIRST' ? 1 : $self->{'CURRENT_PAGE'} - 1));
531 0           $string .= qq() . $self->{"${_}_HTML"} . q();
532             }
533             elsif(($_ eq 'NEXT' || $_ eq 'LAST') && $self->{'CURRENT_PAGE'} != $total_pages)
534             {
535 0 0         my $url = $self->get_page_link(($_ eq 'LAST' ? $total_pages : $self->{'CURRENT_PAGE'} + 1));
536 0           $string .= qq() . $self->{"${_}_HTML"} . q();
537             }
538             elsif($_ eq 'FIRST' || $_ eq 'PREV' || $_ eq 'NEXT' || $_ eq 'LAST')
539             {
540 0           $string .= $self->{"${_}_HTML"};
541             }
542             elsif($_ != $self->{'CURRENT_PAGE'})
543             {
544 0           my $url = $self->get_page_link($_);
545 0           $string .= qq($_);
546             }
547             else
548             {
549 0           $string .= $_;
550             }
551              
552 0           $string .= '
553             }
554              
555             # paging footer
556 0           $string .= '
';
557              
558 0           return $string;
559             }
560              
561             =item B
562              
563             $sql_limit_snippet = $self->get_limit();
564              
565             Given the driver name, returns a limit clause compatible with that driver. If the driver is not
566             supported, generates a warning and returns false.
567              
568             =cut
569              
570             sub get_limit
571             {
572 0     0 1   my $self = shift;
573 0           my ($driver) = @_;
574 0           my $method = "get_${driver}_limit";
575              
576 0           my $to_return = '';
577              
578 0 0         if($self->{'RESULTS_PER_PAGE'} != 0)
579             {
580             eval
581 0           {
582 0           $to_return = $self->$method();
583             };
584              
585 0 0         if($@)
586             {
587 0           warn "error: driver $driver not supported in get_limit()";
588             }
589             }
590              
591 0           return $to_return;
592             }
593              
594             =item B
595              
596             $sql_limit_snippet = $self->get_sort();
597              
598             Given the driver name, returns a sort clause compatible with that driver. If the driver is not
599             supported, generates a warning and returns false.
600              
601             =cut
602              
603             sub get_sort
604             {
605 0     0 1   my $self = shift;
606 0           my ($driver) = @_;
607 0           my $method = "get_${driver}_sort";
608              
609 0           my $to_return = '';
610              
611             eval
612 0           {
613 0           $to_return = $self->$method();
614             };
615              
616 0 0         if($@)
617             {
618 0           warn "error: driver $driver not supported in get_sort()";
619             }
620              
621 0           return $to_return;
622             }
623              
624             =item B
625              
626             $sql_limit_snippet = $self->get_mysql_limit();
627              
628             Generates a MySQL-compliant LIMIT clause to be appended to SQL queries in order to get the
629             appropriate rows for a paged report. Example above, in the SYNOPSIS.
630              
631             =cut
632              
633             sub get_mysql_limit
634             {
635 0     0 1   my $self = shift;
636              
637 0           my $start = ($self->{'CURRENT_PAGE'} - 1) * $self->{'RESULTS_PER_PAGE'};
638 0           my $count = $self->{'RESULTS_PER_PAGE'};
639              
640 0           return "LIMIT $start, $count";
641             }
642              
643             =item B
644              
645             $sql_order_by_snippet = $pager->get_mysql_sort();
646              
647             Returns a MySQL-compliant ORDER BY clause based on the current sorting settings, to be appended
648             to the SQL query used to generate the report that this module is being used for. Example above
649             in the SYNOPSIS.
650              
651             =cut
652              
653             sub get_mysql_sort
654             {
655 0     0 1   my ($self) = @_;
656              
657 0 0 0       if(!$self->{'SORTABLE_COLUMNS'} || ref($self->{'SORTABLE_COLUMNS'}) ne 'ARRAY')
658             {
659 0           die "SORTABLE_COLUMNS is either not defined or not an arrayref, sorting disabled";
660             }
661              
662 0           my $dir = uc($self->{'CURRENT_SORT_DIR'});
663 0           my $sort = $self->{'CURRENT_SORT_COL'};
664 0           my $to_return = '';
665              
666 0 0 0 0     if($dir && $sort && none { $_->{'get'} eq $sort } @{$self->{'SORTABLE_COLUMNS'}})
  0   0        
  0            
667             {
668 0           die "requested sort '$sort' is impossible, not defined in SORTABLE_COLUMNS";
669             }
670              
671 0 0 0       if($dir && $sort)
672             {
673 0     0     my $index = firstidx { $_->{'get'} eq $sort } @{$self->{'SORTABLE_COLUMNS'}};
  0            
  0            
674 0 0         if($self->{'SORTABLE_COLUMNS'}->[$index]->{'get'} ne $sort)
675             {
676 0           die "This should not happen";
677             }
678 0   0       $sort = $self->{'SORTABLE_COLUMNS'}->[$index]->{'order'} || $self->{'SORTABLE_COLUMNS'}->[$index]->{'sql'};
679 0 0         if($sort =~ / AS /i)
680             {
681 0           $sort =~ s/^.+ AS (.+)$/$1/i;
682             }
683 0           $to_return = "ORDER BY $sort $dir";
684             }
685              
686 0           return $to_return;
687             }
688              
689             =item B
690              
691             $sql_limit_snippet = $self->get_Pg_limit();
692              
693             Generates a PostgreSQL-compliant LIMIT clause to be appended to SQL queries in order to get the
694             appropriate rows for a paged report. Example above, in the SYNOPSIS.
695              
696             =cut
697              
698             sub get_Pg_limit
699             {
700 0     0 1   my $self = shift;
701              
702 0           my $start = ($self->{'CURRENT_PAGE'} - 1) * $self->{'RESULTS_PER_PAGE'};
703 0           my $count = $self->{'RESULTS_PER_PAGE'};
704              
705 0           return "LIMIT $count OFFSET $start";
706             }
707              
708             =item B
709              
710             $sql_order_by_snippet = $pager->get_Pg_sort();
711              
712             Returns a PostgreSQL-compliant ORDER BY clause based on the current sorting settings, to be appended
713             to the SQL query used to generate the report that this module is being used for. Example above
714             in the SYNOPSIS.
715              
716             =cut
717              
718             # The MySQL syntax is compatible with PostgreSQL
719             *get_Pg_sort = \&get_mysql_sort;
720              
721             =item B
722              
723             $sql_limit_snippet = $self->get_SQLite_limit();
724              
725             Generates a SQLite-compliant LIMIT clause to be appended to SQL queries in order to get the
726             appropriate rows for a paged report. Example above, in the SYNOPSIS.
727              
728             =cut
729              
730             # The PostgreSQL syntax is compatible with SQLite
731             *get_SQLite_limit = \&get_Pg_limit;
732              
733             =item B
734              
735             $sql_order_by_snippet = $pager->get_SQLite_sort();
736              
737             Returns a SQLite-compliant ORDER BY clause based on the current sorting settings, to be appended
738             to the SQL query used to generate the report that this module is being used for. Example above
739             in the SYNOPSIS.
740              
741             =cut
742              
743             # The MySQL syntax is compatible with SQLite
744             *get_SQLite_sort = \&get_mysql_sort;
745              
746             =item B
747              
748             $url = $pager->get_sort_link($column);
749              
750             Same as get_page_link() above, except allows you to specify the new sort instead of the new page.
751             When specifying the sort column, specifying the same column that is currently selected results
752             in the link being generated for the opposite of its current direction. Otherwise, each column
753             defaults to sort ascending.
754              
755             Additionally, when changing the sort, page is not preserved, the logic being that you likely
756             want to start back at the beginning of the report to view the first I records instead of being
757             stuck in the middle of the record set.
758              
759             =cut
760              
761             sub get_sort_link
762             {
763 0     0 1   my ($self, $sort) = @_;
764              
765 0 0 0       if(!$self->{'SORTABLE_COLUMNS'} || ref($self->{'SORTABLE_COLUMNS'}) ne 'ARRAY')
766             {
767 0           die "SORTABLE_COLUMNS is either not defined or not an arrayref, sorting disabled";
768             }
769              
770 0 0   0     if(none { $_->{'get'} eq $sort } @{$self->{'SORTABLE_COLUMNS'}})
  0            
  0            
771             {
772 0           die "requested sort '$sort' is impossible, not defined in SORTABLE_COLUMNS";
773             }
774              
775             # Either you're switching to a new sort col (default ASC) or you're changing the direction of the current sort
776 0 0 0       my $dir = (!defined($self->{'CURRENT_SORT_COL'}) || $sort ne $self->{'CURRENT_SORT_COL'} ? 'ASC' :
    0          
777             ($self->{'CURRENT_SORT_DIR'} eq 'ASC' ? 'DESC' : 'ASC'));
778              
779             # save the old page number and sort (this is necessary since we have a shared CGI object)
780 0           my $oldpage = $self->{'CGI_OBJECT'}->param($self->{'PAGE_VARIABLE'});
781 0           my $oldsort = $self->{'CGI_OBJECT'}->param($self->{'SORT_VARIABLE'});
782              
783             # set the new sort option, delete page (reset to page 1 on new sort)
784 0           $self->{'CGI_OBJECT'}->param($self->{'SORT_VARIABLE'}, "$sort,$dir");
785 0           $self->{'CGI_OBJECT'}->delete($self->{'PAGE_VARIABLE'});
786 0           my $url = $self->{'CGI_OBJECT'}->url( -absolute => 1, -query => 1, -path_info => 1 );
787              
788             # restore the old page and sort
789 0 0         if($oldsort)
790             {
791 0           $self->{'CGI_OBJECT'}->param($self->{'SORT_VARIABLE'}, $oldsort);
792             }
793             else
794             {
795 0           $self->{'CGI_OBJECT'}->delete($self->{'SORT_VARIABLE'});
796             }
797              
798 0 0         if($oldpage)
799             {
800 0           $self->{'CGI_OBJECT'}->param($self->{'PAGE_VARIABLE'}, $oldpage);
801             }
802              
803 0           return $url;
804             }
805              
806             =item B
807              
808             $html_table_header_row = $pager->get_sortable_table_header();
809              
810             Generates the HTML for the table header, containing the column names and (where applicable) links
811             to change the sort column/direction.
812              
813             Since the header defines the columns, the columns need to be the same width as they are for the
814             data. Therefore, we only draw a table row, not a full table as we do with the paging html. This row
815             should probably be the first row of the table that contains the result set.
816              
817             The output relies on stylesheet elements that currently do not have a definition anywhere.
818             You will need to define these stylesheet elements on your own. This will be fixed in a future
819             release, which hopefully will come soon.
820              
821             =cut
822              
823             sub get_sortable_table_header
824             {
825 0     0 1   my ($self) = @_;
826             # since this function calls get_sort_link, I'm not going to do the error checks -- let them fall through
827              
828 0           my $string = '
829              
830 0           foreach my $col (@{$self->{'SORTABLE_COLUMNS'}})
  0            
831             {
832             # $col == the name of the SQL column (used by reportwriter) - also used as the 1st part of the sort GET variable
833             # $self->{'SORTABLE_COLUMNS'}->{$col} = {
834             # display_name => 'Display Name',
835             # sortable => 1|0,
836             #};
837              
838 0 0         my $url = ($col->{'sortable'} ? $self->get_sort_link($col->{'get'}) : '');
839 0 0 0       $string .= ''
    0          
    0          
    0          
840             . ($url ? qq() : '')
841             . $col->{'display'}
842             . ($url ? ' ' : '')
843             . ( !defined($self->{'CURRENT_SORT_COL'}) || $col->{'get'} ne $self->{'CURRENT_SORT_COL'} ? '' : ($self->{'CURRENT_SORT_DIR'} eq 'ASC' ? $self->{'ASC_HTML'} : $self->{'DESC_HTML'}) )
844             . '
845             }
846              
847 0           $string .= '
848              
849 0           return $string;
850             }
851              
852             1;
853              
854             =back
855              
856             =head1 TODO
857              
858             =over
859              
860             =item *
861             allow for overrideable class names on the table elements
862              
863             =item *
864             purely CSS design?
865              
866             =back
867              
868             =head1 BUGS
869              
870             None are known at this time.
871              
872             Please report any additional bugs discovered to the author.
873              
874             =head1 SEE ALSO
875              
876             This module relies indirectly on the use of the L and the L