1package Data::Page;
2use Carp;
3use strict;
4use base 'Class::Accessor::Chained::Fast';
5__PACKAGE__->mk_accessors(qw(total_entries entries_per_page current_page));
6
7use vars qw($VERSION);
8$VERSION = '2.00';
9
10sub new {
11  my $class = shift;
12  my $self  = {};
13  bless($self, $class);
14
15  my ($total_entries, $entries_per_page, $current_page) = @_;
16  $self->total_entries($total_entries       || 0);
17  $self->entries_per_page($entries_per_page || 10);
18  $self->current_page($current_page         || 1);
19  return $self;
20}
21
22sub entries_per_page {
23  my $self             = shift;
24  my $entries_per_page = $_[0];
25  if (@_) {
26    croak("Fewer than one entry per page!") if $entries_per_page < 1;
27    return $self->_entries_per_page_accessor(@_);
28  }
29  return $self->_entries_per_page_accessor();
30}
31
32sub current_page {
33  my $self = shift;
34  if (@_) {
35    return $self->_current_page_accessor(@_);
36  }
37  return $self->first_page unless defined $self->_current_page_accessor;
38  return $self->first_page if $self->_current_page_accessor < $self->first_page;
39  return $self->last_page  if $self->_current_page_accessor > $self->last_page;
40  return $self->_current_page_accessor();
41}
42
43sub total_entries {
44  my $self = shift;
45  if (@_) {
46    return $self->_total_entries_accessor(@_);
47  }
48  return $self->_total_entries_accessor;
49}
50
51sub entries_on_this_page {
52  my $self = shift;
53
54  if ($self->total_entries == 0) {
55    return 0;
56  } else {
57    return $self->last - $self->first + 1;
58  }
59}
60
61sub first_page {
62  my $self = shift;
63
64  return 1;
65}
66
67sub last_page {
68  my $self = shift;
69
70  my $pages = $self->total_entries / $self->entries_per_page;
71  my $last_page;
72
73  if ($pages == int $pages) {
74    $last_page = $pages;
75  } else {
76    $last_page = 1 + int($pages);
77  }
78
79  $last_page = 1 if $last_page < 1;
80  return $last_page;
81}
82
83sub first {
84  my $self = shift;
85
86  if ($self->total_entries == 0) {
87    return 0;
88  } else {
89    return (($self->current_page - 1) * $self->entries_per_page) + 1;
90  }
91}
92
93sub last {
94  my $self = shift;
95
96  if ($self->current_page == $self->last_page) {
97    return $self->total_entries;
98  } else {
99    return ($self->current_page * $self->entries_per_page);
100  }
101}
102
103sub previous_page {
104  my $self = shift;
105
106  if ($self->current_page > 1) {
107    return $self->current_page - 1;
108  } else {
109    return undef;
110  }
111}
112
113sub next_page {
114  my $self = shift;
115
116  $self->current_page < $self->last_page ? $self->current_page + 1 : undef;
117}
118
119# This method would probably be better named 'select' or 'slice' or
120# something, because it doesn't modify the array the way
121# CORE::splice() does.
122sub splice {
123  my ($self, $array) = @_;
124  my $top = @$array > $self->last ? $self->last : @$array;
125  return () if $top == 0;    # empty
126  return @{$array}[ $self->first - 1 .. $top - 1 ];
127}
128
129sub skipped {
130  my $self = shift;
131
132  my $skipped = $self->first - 1;
133  return 0 if $skipped < 0;
134  return $skipped;
135}
136
1371;
138
139__END__
140
141=head1 NAME
142
143Data::Page - help when paging through sets of results
144
145=head1 SYNOPSIS
146
147  use Data::Page;
148
149  my $page = Data::Page->new();
150  $page->total_entries($total_entries);
151  $page->entries_per_page($entries_per_page);
152  $page->current_page($current_page);
153
154  print "         First page: ", $page->first_page, "\n";
155  print "          Last page: ", $page->last_page, "\n";
156  print "First entry on page: ", $page->first, "\n";
157  print " Last entry on page: ", $page->last, "\n";
158
159=head1 DESCRIPTION
160
161When searching through large amounts of data, it is often the case
162that a result set is returned that is larger than we want to display
163on one page. This results in wanting to page through various pages of
164data. The maths behind this is unfortunately fiddly, hence this
165module.
166
167The main concept is that you pass in the number of total entries, the
168number of entries per page, and the current page number. You can then
169call methods to find out how many pages of information there are, and
170what number the first and last entries on the current page really are.
171
172For example, say we wished to page through the integers from 1 to 100
173with 20 entries per page. The first page would consist of 1-20, the
174second page from 21-40, the third page from 41-60, the fourth page
175from 61-80 and the fifth page from 81-100. This module would help you
176work this out.
177
178=head1 METHODS
179
180=head2 new
181
182This is the constructor, which takes no arguments.
183
184  my $page = Data::Page->new();
185
186There is also an old, deprecated constructor, which currently takes
187two mandatory arguments, the total number of entries and the number of
188entries per page. It also optionally takes the current page number:
189
190  my $page = Data::Page->new($total_entries, $entries_per_page, $current_page);
191
192=head2 total_entries
193
194This method get or sets the total number of entries:
195
196  print "Entries:", $page->total_entries, "\n";
197
198=head2 entries_per_page
199
200This method gets or sets the total number of entries per page (which
201defaults to 10):
202
203  print "Per page:", $page->entries_per_page, "\n";
204
205=head2 current_page
206
207This method gets or sets the current page number (which defaults to 1):
208
209  print "Page: ", $page->current_page, "\n";
210
211=head2 entries_on_this_page
212
213This methods returns the number of entries on the current page:
214
215  print "There are ", $page->entries_on_this_page, " entries displayed\n";
216
217=head2 first_page
218
219This method returns the first page. This is put in for reasons of
220symmetry with last_page, as it always returns 1:
221
222  print "Pages range from: ", $page->first_page, "\n";
223
224=head2 last_page
225
226This method returns the total number of pages of information:
227
228  print "Pages range to: ", $page->last_page, "\n";
229
230=head2 first
231
232This method returns the number of the first entry on the current page:
233
234  print "Showing entries from: ", $page->first, "\n";
235
236=head2 last
237
238This method returns the number of the last entry on the current page:
239
240  print "Showing entries to: ", $page->last, "\n";
241
242=head2 previous_page
243
244This method returns the previous page number, if one exists. Otherwise
245it returns undefined:
246
247  if ($page->previous_page) {
248    print "Previous page number: ", $page->previous_page, "\n";
249  }
250
251=head2 next_page
252
253This method returns the next page number, if one exists. Otherwise
254it returns undefined:
255
256  if ($page->next_page) {
257    print "Next page number: ", $page->next_page, "\n";
258  }
259
260=head2 splice
261
262This method takes in a listref, and returns only the values which are
263on the current page:
264
265  @visible_holidays = $page->splice(\@holidays);
266
267=head2 skipped
268
269This method is useful paging through data in a database using SQL
270LIMIT clauses. It is simply $page->first - 1:
271
272  $sth = $dbh->prepare(
273    q{SELECT * FROM table ORDER BY rec_date LIMIT ?, ?}
274  );
275  $sth->execute($date, $page->skipped, $page->entries_per_page);
276
277=head1 NOTES
278
279It has been said before that this code is "too simple" for CPAN, but I
280must disagree. I have seen people write this kind of code over and
281over again and they always get it wrong. Perhaps now they will spend
282more time getting the rest of their code right...
283
284=head1 SEE ALSO
285
286Related modules which may be of interest: L<Data::Pageset>,
287L<Data::Page::Tied>, L<Data::SpreadPagination>.
288
289=head1 AUTHOR
290
291Based on code originally by Leo Lapworth, with many changes added by
292by Leon Brocard <acme@astray.com>.
293
294=head1 COPYRIGHT
295
296Copyright (C) 2000-4, Leon Brocard
297
298This module is free software; you can redistribute it or modify it
299under the same terms as Perl itself.
300
301
302