re-inventing the pageset wheel

wickline on 2003-07-09T03:52:40

Matts asked about page set code, and use.perl.org decided that my reply was sufficiently compressible that it probably wasn't meaninfull content... so I'm posting it here to see if I'm allowed to put meaningless things in my own journal ;)

-matt

=item C( I<$set_size>, I<$page_size>, I<$curr_page>, I<$url> )

Given the size of a current result set (number of hits), the size of our result pages (ie: showing a list of ten hits per page), and the number of our current page (zero indexed: ie 0 for the first page in the result set), and the initial URL (to which the zero-indexed page numbers will be appeneded)... Returns HTML for links allowing the user to page through the result set. The links will include a 'previous' and 'next' link where applicable, and will include links to jump to up to eight other result pages by page number. Generally, the script will prefer to show four pages before and four after, but for pages near either end of the result set, that view may be shifted to continue showing eight other pages.

For a general idea, here's a textual representation of different output for all of the pages in a twenty-page result set (_underlined_ items are links and whitespace is only added to hilight patterns in the output)

_next_ page[ 1 _2_ _3_ _4_ _5_ _6_ _7_ _8_ _9..._ ] _prev_ , _next_ page[ _1_ 2 _3_ _4_ _5_ _6_ _7_ _8_ _9..._ ] _prev_ , _next_ page[ _1_ _2_ 3 _4_ _5_ _6_ _7_ _8_ _9..._ ] _prev_ , _next_ page[ _1_ _2_ _3_ 4 _5_ _6_ _7_ _8_ _9..._ ] _prev_ , _next_ page[ _1_ _2_ _3_ _4_ 5 _6_ _7_ _8_ _9..._ ] _prev_ , _next_ page[ _...2_ _3_ _4_ _5_ 6 _7_ _8_ _9_ _10..._ ] _prev_ , _next_ page[ _...3_ _4_ _5_ _6_ 7 _8_ _9_ _10_ _11..._ ] _prev_ , _next_ page[ _...4_ _5_ _6_ _7_ 8 _9_ _10_ _11_ _12..._ ] _prev_ , _next_ page[ _...5_ _6_ _7_ _8_ 9 _10_ _11_ _12_ _13..._ ] _prev_ , _next_ page[ _...6_ _7_ _8_ _9_ 10 _11_ _12_ _13_ _14..._ ] _prev_ , _next_ page[ _...7_ _8_ _9_ _10_ 11 _12_ _13_ _14_ _15..._ ] _prev_ , _next_ page[ _...8_ _9_ _10_ _11_ 12 _13_ _14_ _15_ _16..._ ] _prev_ , _next_ page[ _...9_ _10_ _11_ _12_ 13 _14_ _15_ _16_ _17..._ ] _prev_ , _next_ page[ _...10_ _11_ _12_ _13_ 14 _15_ _16_ _17_ _18..._ ] _prev_ , _next_ page[ _...11_ _12_ _13_ _14_ 15 _16_ _17_ _18_ _19..._ ] _prev_ , _next_ page[ _...12_ _13_ _14_ _15_ 16 _17_ _18_ _19_ _20_ ] _prev_ , _next_ page[ _...12_ _13_ _14_ _15_ _16_ 17 _18_ _19_ _20_ ] _prev_ , _next_ page[ _...12_ _13_ _14_ _15_ _16_ _17_ 18 _19_ _20_ ] _prev_ , _next_ page[ _...12_ _13_ _14_ _15_ _16_ _17_ _18_ 19 _20_ ] _prev_ page[ _...12_ _13_ _14_ _15_ _16_ _17_ _18_ _19_ 20 ]

Note that the output reflects one-based indices even though the input page number is zero-based and the href values will reference zero-based indices. Also note that the url prefix arg should be HTML escaped if it needs it. This method just slaps an int on the end and considers the result to be valid for an href value.

For sufficiently small result sets, fewer than eight links may be shown.

For results sets having only three pages, only the prev/next links need to be shown for the second page (as numbered 1/3 links are overly redundant). Result sets having only (one or) two pages need not show numbered pages links either.

Input is not validated. Caller is expected to have ensured sensible args. The set size must be positive. The page size must be positive. The current page must be non-negative and less than number of pages (which is calculated from the set size and page size... and note that 'less than' is accurate because the current page is zero-indexed).

=cut

sub prepare_resultset_paging { my( $set_size, $page_size, $curr_page, $url ) = @_; my $margin = 4; # in the midst of a large list, keep this many links on # either side of our current page. Shift things about if we're near # either end of our large list to keep 2*$margin links available # for sufficiently short lists, the margin becomes less relevant # and we just show as many links as there are # determine how many pages are in the result set my $num_pages = int( $set_size / $page_size ); $num_pages++ if $set_size % $page_size; my $max_page = $num_pages - 1; # zero-indexed return '' if $curr_page > $max_page; # docs warn that caller shouldn't do this my $l_margin = $margin; my $r_margin = $margin; if ( $curr_page < $l_margin ) { my $diff = $l_margin - $curr_page; $l_margin -= $diff; $r_margin += $diff; } if ( $curr_page + $r_margin > $max_page ) { my $diff = $curr_page + $r_margin - $max_page; $r_margin -= $diff; while ( $diff-- and ( $curr_page-$l_margin > 0) ) { $l_margin++; } } my @window = ($curr_page - $l_margin) .. ($curr_page + $r_margin); my( $win_links, $prev, $next ) = ( '','','' ); if ( @window > 3 or (@window == 3 and $curr_page != 1) ) { $win_links = join( ' ', map { $_ == $curr_page ? qq{@{[$_+1]}} : qq{@{[$_+1]}} } @window); $win_links =~ s{^(]+>)}{$1...} if $window[0]; $win_links =~ s{\z}{...} if $window[-1] < $max_page; $win_links = "; page[ $win_links ]"; } $prev = qq{previous page} if $curr_page; $next = qq{next page} if $curr_page < $max_page; return join( ', ', grep {$_} ($prev, $next) ).$win_links }