Version: 2.0.1

Type: Function

Category: HTML

License: GNU General Public License

Description: Returns XHTML for a sequence of links la "search results". Doesn't need a database to function; you just specify how many items there are to display, how many to display per page and what HREF the links should point to. The XHTML is easily styled with CSS. To prevent very long navbars, redundant links are removed when needed.



<?php
# "prev 123 next" navbar, version 2.0.1 (October 19 2002)
# Functions that can print out links to several pages in a sequence.
# You can run this script as is to see a demo.
###########################################################
# Demo code

# Sample style.
# You might want to include something similar in your style sheet.
print '<style type="text/css">
p.navigation{
	font:0.7em verdana;
	text-align:center;
}
p.navigation strong.active, p.navigation a.prev, p.navigation a.next{
	font-weight:bold;
	font-size:1.25em;
	line-height:0.8em;
	vertical-align:bottom;
	text-decoration:none;
}
</style>';

# Sample implementations.
# Try clicking on the links they generate.
$pre_href = $PHP_SELF.'?page_no=';
$post_href = '';
$page_no = $HTTP_GET_VARS['page_no'];
print navigation($pre_href, $post_href, 1548,   25, $page_no);
print navigation($pre_href, $post_href, 9465,   20, $page_no);
print navigation($pre_href, $post_href, 33487,  10, $page_no);
print navigation($pre_href, $post_href, 334870, 5,  $page_no);

# End demo code
###########################################################
# The functions

/*
	The main function:
	Returns HTML string with a navigation bar, easily styled with CSS.
	Returns false if no navigation bar was necessary (if everything 
	could fit on one page).

	$pre_href = everyhing in the links' HREF before the active page's number.
	$post_href = everyhing *after* the active page's number.
	$num_items = total number of items available.
	$items_per_page = well, d'uh!
	$active = the active page's number.
	$nearby = minimum number of nearby pages to $active to display.
	$threshold = the smaller the number, the more restrictive the 
	  selection of links will be when $total is very high. This is to 
	  conserve space.

	These default settings limit the number of links to a maximum 
	of about 20.
*/
function navigation($pre_href='?page=', $post_href='', $num_items=0, $items_per_page=25, $active=1, $nearby=5, $threshold=100)
{
	# &#8230; is the ellipse character: "..."
	$space = '<span class="spacer"> &#8230; '."\n\t".'</span>';

	# There's no point in printing this string if there are no items,
	# Or if they all fit on one page.
	if ($num_items > 0 && $num_items > $items_per_page)
	{
		# STEP 1:
		# Force variables into certain values.

		# $items_per_page can't be smaller than 1!
		# Also, avoid division by zero later on.
		$items_per_page = max($items_per_page, 1);

		# Calculate the number of listing pages.
		$total = ceil($num_items/$items_per_page);

		# $active can't be higher than $total or smaller than 1!
		$active = max( min($active,$total), 1);

		# STEP 2:
		# Do the rest.

		# Get the sequence of pages to show links to.
		$pages = navigationSequence($total, $active, $nearby, $threshold);
		
		# Print a descriptive string.
		$first = ($active-1)*$items_per_page + 1;
		$last  = min($first+$items_per_page-1, $num_items);
		if ($first == $last)
			$listing = $first;
		else
			# &#8211; is the EN dash, the proper hyphen to use.
			$listing = $first.'&#8211;'.$last;
		$r = '<p class="navigation">'."\n\tShowing $listing of $num_items<br />\n";
	
		# Initialize the list of links.
		$links = array();
	
		# Add "previous" link.
		if ($active > 1 && $total > 1)
			$links[] = '<a href="'.$pre_href.($active-1).$post_href.
			           '" class="prev" title="Previous">&laquo;</a>';
	
		# Decide how the each link should be presented.
		for($i=0; $i<sizeof($pages); $i++)
		{
			# Current link.
			$curr = $pages[$i];

			# See if we should any $spacer in connection to this link.
			if ($i>0 AND $i<sizeof($pages)-1)
			{
				$prev = $pages[$i-1];
				$next = $pages[$i+1];

				# See if we should any $spacer *before* this link.
				# (Don't add one if the last link is already a spacer.)
				if ($prev < $curr-1 AND $links[sizeof($links)-1] != $space)
					$links[] = $space;
			}

			# Add the link itself!
			# If the link is not the active page, link it.
			if ($curr != $active)
				$links[] = '<a href="'.$pre_href.$curr.$post_href.'">'.$curr.'</a>';
			# Else don't link it.
			else
				$links[] = '<strong class="active">'.$active.'</strong>';

			if ($i>0 AND $i<sizeof($pages)-1)
			{
				# See if we should any $spacer *after* this link.
				# (Don't add one if the last link is already a spacer.)
				if ($next > $curr+1 AND $links[sizeof($links)-1] != $space)
					$links[] = $space;
			}
		}
	
		# Add "next" link.
		if ($active < $total && $total > 1)
			$links[] = '<a href="'.$pre_href.($active+1).$post_href.
			           '" class="next" title="Next">&raquo;</a>';
	
		# Put it all together.
		$r .= "\t".implode($links, "\n\t")."\n</p>\n";
		$r = str_replace("\n\t".$space."\n\t", $space, $r);

		return $r;
	}
	else
		return false;
}

# This one is used by navigation().
function navigationSequence($total=1, $active=1, $nearby=5, $threshold=100)
{
	# STEP 1:
	# Force minimum and maximum values.
	$total     = (int)max($total, 1);
	$active    = (int)min( max($active,1), $total);
	$nearby    = (int)max($nearby, 2);
	$threshold = (int)max($threshold, 1);

	# STEP 2:
	# Initialize $pages.

	# Array where each key is a page.
	# That way we can easily overwrite duplicate keys, without
	# worrying about the order of elements.
	# Begin by adding the first, the active and the final page.
	$pages = array(1=>1, $active=>1, $total=>1);

	# STEP 3:
	# Add nearby pages.
	for ($diff=1; $diff<$nearby+1; $diff++)
	{
		$pages[min( max($active-$diff,1), $total)] = 1;
		$pages[min( max($active+$diff,1), $total)] = 1;
	}

	# STEP 4:
	# Add distant pages.

	# What are the greatest and smallest distances between page 1 
	# and active page, or between active page and final page?
	$biggest_diff  = max($total-$active, $active);
	$smallest_diff = min($total-$active, $active);

	# The lower $factor is, the bigger jumps (and ergo fewer pages) 
	# there will be between the distant pages.
	$factor = 0.75;
	# If we think there will be too many distant pages to list, 
	# reduce $factor.
	while(pow($total*max($smallest_diff,1), $factor) > $threshold)
		$factor *= 0.9;

	# Add a page between $active and the farthest end (both upwards 
	# and downwards). Then add a page between *that* page and 
	# active. And so on, until we touch the nearby pages.
	for ($diff=round($biggest_diff*$factor); $diff>$nearby; $diff=round($diff*$factor))
	{
		# Calculate the numbers.
		$lower  = $active-$diff;
		$higher = $active+$diff;

		# Round them to significant digits (for readability):
		# Significant digits should be half or less than half of 
		# the total number of digits, but at least 2.
		$lower  = round($lower,  -floor(max(strlen($lower),2)/2));
		$higher = round($higher, -floor(max(strlen($higher),2)/2));

		# Maxe sure they're within the valid range.
		$lower  = min(max($lower,  1),$total);
		$higher = min(max($higher, 1),$total);

		# Add them.
		$pages[$lower]  = 1;
		$pages[$higher] = 1;
	}

	# STEP 5:
	# Convert the keys into values and sort them.
	$return = array_keys($pages);
	sort($return);

	return $return;
}

?>