File Coverage

blib/lib/Data/SpreadPagination.pm
Criterion Covered Total %
statement 82 82 100.0
branch 56 56 100.0
condition 15 15 100.0
subroutine 12 12 100.0
pod 5 5 100.0
total 170 170 100.0


line stmt bran cond sub pod time code
1             package Data::SpreadPagination;
2              
3 1     1   42650 use strict;
  1         2  
  1         42  
4 1     1   6 use Carp;
  1         2  
  1         94  
5              
6 1     1   1078 use Data::Page;
  1         12296  
  1         12  
7 1     1   1848 use POSIX qw(ceil floor);
  1         18258  
  1         10  
8 1     1   5053 use Math::Round qw(round);
  1         4776  
  1         239  
9              
10 1     1   9 use vars qw(@ISA $VERSION);
  1         2  
  1         1441  
11             @ISA = qw(Data::Page);
12             $VERSION = '0.1.2';
13              
14             =head1 NAME
15              
16             Data::SpreadPagination - Page numbering and spread pagination
17              
18             =head1 SYNOPSIS
19              
20             use Data::SpreadPagination;
21             my $pageInfo = Data::SpreadPagination->new({
22             totalEntries => $totalEntries,
23             entriesPerPage => $entriesPerPage,
24             # Optional, will use defaults otherwise.
25             # only 1 of currentPage / startEntry can be provided.
26             currentPage => $currentPage,
27             startEntry => $startEntry,
28             maxPages => $maxPages,
29             });
30              
31             # General page information
32             print " First page: ", $pageInfo->first_page, "\n";
33             print " Last page: ", $pageInfo->last_page, "\n";
34             print " Next page: ", $pageInfo->next_page, "\n";
35             print " Previous page: ", $pageInfo->previous_page, "\n";
36              
37             # Results on current page
38             print "First entry on page: ", $pageInfo->first, "\n";
39             print " Last entry on page: ", $pageInfo->last, "\n";
40              
41             # Page range information
42             my $pageRanges = $pageInfo->page_ranges;
43              
44             # Print out the page spread
45             foreach my $page ($pageInfo->pages_in_spread()) {
46             if (!defined $page) {
47             print "... ";
48             } elsif ($page == $pageInfo->current_page) {
49             print "$page ";
50             } else {
51             print "$page ";
52             }
53             }
54              
55             =head1 DESCRIPTION
56              
57             The object produced by Data::SpreadPagination can be used to create
58             a spread pagination navigator. It inherits from Data::Page, and has
59             access to all of the methods from this object.
60              
61             In addition, it also provides methods for creating a pagination spread,
62             to allow for keeping the number of pagenumbers displayed within a sensible
63             limit, but at the same time allowing easy navigation.
64              
65             The object can easily be passed to a templating system
66             such as Template Toolkit or be used within a script.
67              
68             =head1 METHODS
69              
70             =head2 new()
71              
72             my $pageInfo = Data::SpreadPagination->new({
73             totalEntries => $totalEntries,
74             entriesPerPage => $entriesPerPage,
75             # Optional, will use defaults otherwise.
76             # only 1 of currentPage / startEntry can be provided.
77             currentPage => $currentPage,
78             startEntry => $startEntry,
79             maxPages => $maxPages,
80             });
81              
82             This is the constructor of the object. It requires an anonymous
83             hash containing the 'totalEntries', how many data units you have,
84             and the number of 'entriesPerPage' to display. Optionally the
85             'currentPage' / 'startEntry' (defaults to page/entry 1) and
86             'maxPages' (how many pages to display in addition to the current
87             page) can be added.
88              
89             =cut
90              
91             sub new {
92 59     59 1 44355 my $class = shift;
93 59         92 my %params = %{shift()};
  59         327  
94              
95 59 100 100     960 croak "totalEntries and entriesPerPage must be supplied"
96             unless defined $params{totalEntries} and defined $params{entriesPerPage};
97              
98 56 100 100     431 croak "currentPage and startEntry can not both be supplied"
99             if defined $params{currentPage} and defined $params{startEntry};
100              
101 55 100 100     148 $params{currentPage} = 1
102             unless defined $params{currentPage} or defined $params{startEntry};
103              
104 55 100       203 $params{currentPage} = int( ($params{startEntry} - 1) / $params{entriesPerPage} ) + 1
105             if defined $params{startEntry};
106              
107 55         413 my $self = $class->SUPER::new($params{totalEntries}, $params{entriesPerPage}, $params{currentPage});
108            
109 55 100       2183 $params{maxPages} = ceil( $params{totalEntries} / $params{entriesPerPage} ) - 1
110             unless defined $params{maxPages};
111              
112 55         120 $self->{MAX_PAGES} = $params{maxPages};
113 55         169 $self->_do_pagination( @params{qw(totalEntries entriesPerPage currentPage maxPages)} );
114              
115 55         178 return $self;
116             }
117              
118             =head2 max_pages()
119              
120             print "Maximum additional pages to display is ", $pageInfo->max_pages(), "\n";
121              
122             This method returns the maximum number of pages that are included in the
123             spread pagination in addition to the current page.
124              
125             =cut
126              
127             sub max_pages {
128 56     56 1 1019 my $self = shift;
129              
130 56         119 return $self->{MAX_PAGES};
131             }
132              
133             =head2 page_ranges()
134              
135             $ranges = $pageInfo->page_ranges();
136             for my $qtr (1..4) {
137             my $range = $ranges->[$qtr-1];
138             if (defined $range) {
139             print "Qtr $qtr: no pages\n";
140             } else {
141             print "Qtr $qtr: pages " . $range->[0] . " to " . $range->[1] . "\n";
142             }
143             }
144              
145             This method returns either an array or an arrayref (based upon context)
146             of the page ranges for each of the four quarters in the spread. Each range
147             is either undef for an empty quarter, or an array of the lower and upper
148             pages in the range.
149              
150             =cut
151              
152             sub page_ranges {
153 100     100 1 25633 my $self = shift;
154              
155 100 100       274 return wantarray ? @{ $self->{PAGE_RANGES} } : $self->{PAGE_RANGES};
  50         480  
156             }
157              
158             =head2 pages_in_spread_raw()
159              
160             # Print out the page spread
161             foreach my $page ($pageInfo->pages_in_spread_raw()) {
162             if ($page == $pageInfo->current_page) {
163             print "$page ";
164             } else {
165             print "$page ";
166             }
167             }
168              
169             This method returns either an array or an arrayref (based upon context)
170             of purely the page numbers within the spread.
171              
172             =cut
173              
174             sub pages_in_spread_raw {
175 100     100 1 26547 my $self = shift;
176 100         161 my $pages = [];
177              
178 100         216 for (0..3) {
179 400 100       5884 push @$pages, $self->{PAGE_RANGES}[$_][0] .. $self->{PAGE_RANGES}[$_][1]
180             if defined $self->{PAGE_RANGES}[$_];
181              
182 400 100       1212 push @$pages, $self->current_page()
183             if $_ == 1;
184             }
185            
186 100 100       472 return wantarray ? @{ $pages } : $pages;
  50         363  
187             }
188              
189             =head2 pages_in_spread()
190              
191             # Print out the page spread
192             foreach my $page ($pageInfo->pages_in_spread()) {
193             if (!defined $page) {
194             print "... ";
195             } elsif ($page == $pageInfo->current_page) {
196             print "$page ";
197             } else {
198             print "$page ";
199             }
200             }
201              
202             This method returns either an array or an arrayref (based upon context)
203             of the page numbers within the spread. Breaks in the sequence are
204             indicated with undef's.
205              
206             =cut
207              
208             sub pages_in_spread {
209 100     100 1 24590 my $self = shift;
210 100         147 my $ranges = $self->{PAGE_RANGES};
211 100         151 my $pages = [];
212              
213 100 100       284 if (!defined $ranges->[0]) {
214 44 100       118 push @$pages, undef if $self->current_page > 1;
215             } else {
216 56         161 push @$pages, $ranges->[0][0] .. $ranges->[0][1];
217 56 100 100     280 push @$pages, undef if defined $ranges->[1] and ($ranges->[1][0] - $ranges->[0][1]) > 1;
218             }
219              
220 100 100       2118 push @$pages, $ranges->[1][0] .. $ranges->[1][1] if defined $ranges->[1];
221 100         245 push @$pages, $self->current_page;
222 100 100       4412 push @$pages, $ranges->[2][0] .. $ranges->[2][1] if defined $ranges->[2];
223              
224 100 100       182 if (!defined $ranges->[3]) {
225 44 100       115 push @$pages, undef if $self->current_page < $self->last_page;
226             } else {
227 56 100 100     222 push @$pages, undef if defined $ranges->[2] and ($ranges->[3][0] - $ranges->[2][1]) > 1;
228 56         236 push @$pages, $ranges->[3][0] .. $ranges->[3][1];
229             }
230              
231 100 100       2749 return wantarray ? @{ $pages } : $pages;
  50         162  
232             }
233              
234             # Carry out the pagination calculations
235             # Algorithm description reverse-engineered from Squirrelmail
236             # Reimplemented from description only by Alex Gough
237             sub _do_pagination {
238 55     55   110 my $self= shift;
239 55         132 my $total_entries = $self->total_entries;
240 55         477 my $entries_per_page = $self->entries_per_page;
241 55         478 my $current_page = $self->current_page;
242 55         2179 my $max_pages = $self->max_pages;
243              
244             # qNsizes
245 55         85 my @q_size = ();
246 55         61 my ($add_pages, $adj);
247              
248             # step 2
249 55         263 my $total_pages = ceil($total_entries / $entries_per_page);
250 55 100       200 my $visible_pages = $max_pages < ($total_pages-1)
251             ? $max_pages
252             : $total_pages - 1;
253 55 100       113 if ($total_pages - 1 <= $max_pages) {
254 15         39 @q_size = ($current_page - 1, 0, 0, $total_pages - $current_page);
255             }
256             else {
257 40         199 @q_size = (floor($visible_pages / 4),
258             round($visible_pages / 4),
259             ceil($visible_pages / 4),
260             round( ($visible_pages - round($visible_pages/4) )/3) );
261 40 100       1059 if ($current_page - $q_size[0] < 1) {
    100          
    100          
    100          
262 2         6 $add_pages = $q_size[0] + $q_size[1] - $current_page +1;
263 2         18 @q_size = ($current_page -1, 0, $q_size[2] + ceil($add_pages/2),
264             $q_size[3] + floor($add_pages /2));
265             }
266             elsif ($current_page - $q_size[1] - ceil($q_size[1] / 3)<=$q_size[0]) {
267 6         17 $adj = ceil((3*($current_page - $q_size[0] - 1))/4);
268 6         9 $add_pages = $q_size[1] - $adj;
269 6         35 @q_size = ($q_size[0], $adj, $q_size[2] + ceil($add_pages/2),
270             $q_size[3] + floor($add_pages/2));
271             }
272             elsif ($current_page + $q_size[3] >= $total_pages) {
273 5         12 $add_pages = $q_size[2] + $q_size[3] - $total_pages +$current_page;
274 5         26 @q_size = ($q_size[0] + floor($add_pages / 2),
275             $q_size[1] + ceil($add_pages / 2), 0,
276             $total_pages - $current_page);
277             }
278             elsif ($current_page + $q_size[2] >= $total_pages - $q_size[3]) {
279 4         15 $adj = ceil((3*($total_pages - $current_page - $q_size[3]))/4);
280 4         5 $add_pages = $q_size[2] - $adj;
281 4         20 @q_size = ($q_size[0] + floor($add_pages/2),
282             $q_size[1] + ceil($add_pages/2), $adj, $q_size[3]);
283             }
284             }
285             # step 3 (PROFIT)
286 55 100       464 $self->{PAGE_RANGES} = [ $q_size[0] == 0 ? undef
    100          
    100          
    100          
287             : [1,$q_size[0]],
288             $q_size[1] == 0 ? undef
289             : [$current_page - $q_size[1], $current_page-1],
290             $q_size[2] == 0 ? undef
291             : [$current_page+1, $current_page+$q_size[2]],
292             $q_size[3] == 0 ? undef
293             : [$total_pages - $q_size[3] + 1, $total_pages],
294             ];
295              
296             }
297              
298             =head1 BUGS
299              
300             Hopefully there aren't any nasty bugs lurking in here anywhere.
301             However, if you do find one, please report it via RT.
302              
303             =head1 ALGORITHM
304              
305             The algorithm used to create the pagination spread was reverse-engineered
306             out of Squirrelmail by myself, and then reimplemented from description
307             only by Alex Gough.
308              
309             =head1 THANKS, MANY
310              
311             Alex Gough for implementing the central algorithm from my description.
312              
313             =head1 AUTHOR
314              
315             Jody Belka C
316              
317             =head1 SEE ALSO
318              
319             L.
320              
321             =head1 COPYRIGHT AND LICENSE
322              
323             Copyright 2004 by Jody Belka
324              
325             This library is free software; you can redistribute it and/or modify
326             it under the same terms as Perl itself.
327              
328             =cut
329              
330             <
331             Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about
332             being on some really good drugs -- you know, there is no spoon. - flyingmoose
333             QUOTE