Tuesday, August 11, 2009

BUILDING THE PAGE NAVIGATOR CLASS

When there are a large number of images in a directory, it’s not desirable to display
all of them on one web page because doing so will probably create a very large and long page.
Web pages should be of reasonable length and should not take too long to download. Rather than dumping all your images onto one page, use a page navigator to step through them in an orderly fashion. This chapter will take on the task of creating a navigator class; Chapter 8 will use this class in conjunction with the DirectoryItems class.
Before you can create a page navigator, you need to determine how it should behave. Keep its design flexible and make sure that its appearance is easily configurable so that it can blend with the style of any particular page.

How Will the Navigator Behave?

A good starting point is to look at the navigator at the bottom of a Google query page. When searching Google, the default settings show 10 results per page and the navigator appears across the bottom of the page. One navigates by clicking the Previous or Next links, by choosing a page number, or by

clicking one of the many “o”s in Google. If your query returns a large number of pages, not all pages are shown in the page navigator. Records are ordered by relevance to the search criteria. Given this ordering scheme, there is little incentive to move to the last page of results and, in fact, there is no easy way of doing so.

Different Kinds of Searches

However, in many cases, searches return a relatively small number of items, and records are often ordered alphabetically. In situations such as this there should be an easy way to move to the beginning and the end pages, in addition to being able to move Previous and Next. Too, as with Google, the ability to configure the number of items shown per page is also desirable.
You should also limit the number of pages or links shown at any one time by your page navigator and make this option configurable to accommodate different needs and situations. For example, if you have 2,000 items to display and you’re showing 10 items per page, it’s probably not advisable to show all
200 links across the bottom of one page. But at the same time, you should show the total number of pages and identify the current page so that the user is not left in the dark.
Finally, the display style of navigation buttons should be configurable so that they match the design of an existing page. The best way to do this is to assign them a class name and manipulate their style using Cascading Style Sheets (CSS).

What Will It Look Like?

In sum, you will design a page navigator that will look something like
Figure 7-1.

Figure 7-1: Your page navigator design

In this particular example, the maximum number of pages shown by your navigator is 7. The total number of pages is 12, and the link to the current page, 4, is disabled (indicated above by an italicized number). Each button and page number functions as a hyperlink (except for that of the current page). The button labeled |< displays the first page, and the button labeled >| displays the last page. In this particular example, the Next button displays page 5, and the Prev button displays page 3.
Now look at Figure 7-1 again, and note that pages 8 through 12 are not displayed. You can go directly to page 12 by clicking the >| button, but there is no way to go directly to pages 8 through 11. At what point should links to these pages become visible?
Apply this question to the Google navigator, and you’ll see that the answer is not very straightforward. Among other things, it depends on the
direction you want to move and the number of items your search returns.

In some situations, the number of page links shown doubles. You probably don’t want to emulate this behavior, because your navigator will be used in a variety of situations, and in some cases space will be at a premium.
If you look more closely at a Google query, you can get a few hints about how to implement other desired behavior. For instance, try the following: Perform a Google query and put your mouse over one of the page number links in the navigator. If you look at the status bar of your browser, you see a query string that includes the variable start. If you haven’t changed Google’s default setting of 10 items per page, the value assigned to this variable is always a multiple of 10 that increases by 10 as the page numbers increase.
You’ll use a similar technique in your page navigator. Your navigator will be a series of hyperlinks, each including a query string containing a page num- ber indicating an offset from the start.

The Code

Go ahead and download the code for your page navigator, and look it over. Notice that there are considerably more data members than in other classes discussed so far. Names have been chosen in an attempt to make the purpose of the variable explicit, and related variables have been grouped. We’ll discuss these variables in the order in which they appear in the class file.

private $pagename;

The variable $pagename is the name of the page that will contain the page navigator control. It could be replaced by $_SERVER['PHP_SELF'], but by using a variable, you can accommodate situations where the Apache module mod_rewrite is being used to rewrite URLs. It’s designed to hold a string.

private $totalpages;

$totalpages is a convenient way to refer to the total number of pages required to display all the items in your list of images. It is calculated from the total number of items and the number of items shown per page. Its value will be an integer.

private $recordsperpage;
private $maxpagesshown;

$recordsperpage is the number of items shown on a page and $maxpagesshown is the maximum number of links to additional pages shown on any one page. The former affects the length of the page, while the latter affects the width of the navigator. Again, these are intended to be integer variables.

private $currentstartpage; private $currentendpage; private $currentpage;

$currentstartpage, $currentendpage, and $currentpage are best understood using a visual example. Refer back to Figure 7-1; these would be 1, 7, and 4, respectively.

The next four variables are string variables that hold the HTML code necessary to display inactive links.

//next and previous inactive private $spannextinactive; private $spanpreviousinactive;
//first and last inactive
private $firstinactivespan;
private $lastinactivespan;

If you are currently on the first page, moving to a previous page or to the first page itself wouldn’t make sense. These variables will be used in place of active hyperlinks. Inactive links will be enclosed by span tags. Assigning a CSS class name to these spans allows their appearance to be manipulated by a style sheet.
$firstparamname and $params are data members that will form the query string in each hyperlink in your navigator.

//must match $_GET['offset'] in calling page private $firstparamname = "offset";
//use as "&amp;name=value" pair for getting
private $params;

$firstparamname is assigned a default value of “offset.” While the additional parameters contained in $params may or may not be added to the query string, the use of the “offset” parameter is not optional; this variable’s name must always be matched by a $_GET['offset'] in the page that contains your navi- gator. The $firstparamname will perform the same function as start in a Google query string—you will always need to know where the current page is relative to the start page. The variable $params will hold any other name/value pairs that may be needed as part of a query string. (You’ll learn more about this in Chapter 9.)
The next set of variables are string values that hold the CSS class names for the page navigator and its elements.

//css class names
private $divwrappername = "navigator";
private $pagedisplaydivname = "totalpagesdisplay";
private $inactivespanname = "inactive";

You’ve assigned default values to each of these variables, but they all have set and get methods so a client programmer can change them in order to match existing CSS classes if need be. $divwrappername is the name of the div tag that encloses the complete navigator. $pagedisplaydivname allows you to separately manipulate the display of the message relating the current page and total number of pages, such as page 4 of 12. You only need one class name for all of your inactive spans, because you want them all to have the same look.

The remaining four variables are simply text strings that label the con- trols used in the navigator, and they can be changed as the user sees fit:

//text for navigation
private $strfirst = "|&lt;"; private $strnext = "Next"; private $strprevious = "Prev"; private $strlast = "&gt;|";
//for error reporting private $errorstring;

The use of variables for the navigation text means that a client program- mer can configure these values—the look of the navigator is not fixed and can be adjusted to accommodate different visual layouts. The final data member is a string variable used for error reporting.

The Constructor

Now let’s see how the class is constructed. The constructor accepts six arguments, two of which have default values. Here is the constructor declaration:

public function __construct($pagename, $totalrecords, $recordsperpage,
$recordoffset, $maxpagesshown = 4, $params = "")

Four of the parameters to the constructor are simply copied into their class equivalents, and all have been discussed in the previous section on the data members.

$this->pagename = $pagename;
$this->recordsperpage = $recordsperpage;
$this->maxpagesshown = $maxpagesshown;
//already urlencoded
$this->params = $params;

Note that $params (the variable that contains any additional parameters as a name/value pair) is not URL-encoded within the class. If it is used, it will need to be URL-encoded before it is sent.
The constructor finishes with calls to a number of private class methods:

//check recordoffset a multiple of recordsperpage
$this->checkRecordOffset($recordoffset, $recordsperpage) or die($this->errorstring);
$this->setTotalPages($totalrecords, $recordsperpage);
$this->calculateCurrentPage($recordoffset, $recordsperpage);
$this->createInactiveSpans();
$this->calculateCurrentStartPage();
$this->calculateCurrentEndPage();

Let’s look at each of these method calls in turn.

Ain’t Misbehavin’

If you want your navigator to behave properly, you can check some of the values passed to the constructor; that’s exactly what the checkRecordOffset method does. It terminates construction of your object if it returns false. Let’s see why.

private function checkRecordOffset($recordoffset, $recordsperpage){
$bln = true;
if($recordoffset%$recordsperpage != 0){
$this->errorstring = "Error - not a multiple of records per page.";
$bln = false;
}
return $bln;
}

The $recordoffset variable passed to the constructor tells the navigator where it is currently positioned.
Since you are paging through your list while keeping the number of items shown per page constant, the record offset must be a multiple of the number of items shown per page. If it’s not, the navigator may still function but its behavior will be erratic. For this reason, the error message variable is set, a value of false is returned, and the application terminates. Terminating the application and identifying the reason saves having to debug a misbehav- ing application.

Other Constructor Method Calls

The five remaining private method calls made from the constructor aren’t quite as interesting as the checkRecordOffset method, but a few comments are appropriate.

Determining the Total Number of Pages
Since your navigator always allows you to move to the last item, you need to know the total number of pages:

private function setTotalPages($totalrecords, $recordsperpage){
$this->totalpages = ceil($totalrecords/$recordsperpage);
}

You use the ceil function to round up, because your final page may be a partial page.
For example, if you have 101 items to display, and you are showing 10
items per page, the first 10 pages will each show 10 items while the 11th page will show only one.

Determining the Current Page
In addition to the total number of pages, you also need to know the current page, so you have a calculateCurrentPage method:

private function calculateCurrentPage($recordoffset, $recordsperpage){
$this->currentpage = $recordoffset/$recordsperpage;
}

Simply dividing the record offset by the records per page gives you the current page. Notice that if you’re at the beginning of your list, the value of $recordoffset is 0, so the first page is also 0. This makes sense from a programming point of view, but before displaying the current page to a user, it’s incremented by 1.

Inactive Spans
The following method—createInactiveSpans—prepares the HTML code necessary to display inactive Next, First, Previous, and Last links:

private function createInactiveSpans(){
$this->spannextinactive = "<span class=\"".
"$this->inactivespanname\">$this->strnext</span>\n";
$this->lastinactivespan = "<span class=\"".
"$this->inactivespanname\">$this->strlast</span>\n";
$this->spanpreviousinactive = "<span class=\"".
"$this->inactivespanname\">$this->strprevious</span>\n";
$this->firstinactivespan = "<span class=\"".
"$this->inactivespanname\">$this->strfirst</span>\n";
}

While setting these variables is not strictly necessary (there may, in fact, not be any inactive spans on a particular page), by creating a method to prepare inactive links beforehand, you unclutter your code and make the logic of the most important method—getNavigator—clearer.

Finding the Start Page
Since links to all pages are not always shown, page 1 is not always the first link on a page. For this reason you need to determine the current start page. For example, if the total number of items is 100 with 5 items per page, and you are showing 4 links in your navigator and the current page is 6, the current start page for the navigator will be 5.

private function calculateCurrentStartPage(){
$temp = floor($this->currentpage/$this->maxpagesshown);
$this->currentstartpage = $temp * $this->maxpagesshown;
}

Calculating the Current End Page
The last page displayed in the navigator is easily calculated once the first page has been determined:

private function calculateCurrentEndPage(){
$this->currentendpage = $this->currentstartpage + $this->maxpagesshown;
if($this->currentendpage > $this->totalpages){
$this->currentendpage = $this->totalpages;
}
}

The current end page is the current page plus the maximum number of pages shown, unless that number is greater than the total number of pages, in which case, the end page is equal to the total number of pages.

The getNavigator Method

We’ve covered the data members, the constructor, and some related private methods of the page navigator class, but it’s the public methods that allow you to use it.
The get and set methods basically allow manipulation or retrieval of the CSS class names for the various components in the navigator, so we won’t spend time on them. The method that performs most of the work in this
class is the getNavigator method. It returns a string of the HTML-encoded links that make up your navigator. The navigator (shown in Figure 7-1) is created by starting at the left with the Move First link and finishes on the right with the Move Last link. We’ll discuss the code piece by piece and relate it back to this figure. The declaration of this method is:

public function getNavigator()

The very first responsibility of this method is to wrap the entire navigator in a div tag and assign a class name to this div. Doing so allows you to manip- ulate the appearance of your navigator via CSS:

$strnavigator = "<div class=\"$this->divwrappername\">\n";

Move First and Move Previous

The first element displayed is the hyperlink that allows you to move to the very first page of items. It’s disabled if the current page is the first page; if the current page is not the first page, you call a private class method— createLink—to create the hyperlink.

//output movefirst button if($this->currentpage == 0){
$strnavigator .= $this->firstinactivespan;

}else{
$strnavigator .= $this->createLink(0, $this->strfirst);
}

The createLink method to create the hyperlink is as follows:

private function createLink($offset, $strdisplay ){
$strtemp = "<a href=\"$this->pagename?$this->firstparamname=";
$strtemp .= $offset;
$strtemp .= "$this->params\">$strdisplay</a>\n";
return $strtemp;
}

This method constructs a hyperlink that includes a query string contain- ing the required offset parameter and any additional parameters that may be needed. For a Move First button, this link appears as |< if the default value of the variable—$strfirst—has not been altered.
The same logic applies to the Move Previous link, which is disabled if the current page is the first page:

//output moveprevious button if($this->currentpage == 0){
$strnavigator .= $this->spanpreviousinactive;
}else{
$strnavigator .= $this->createLink($this->currentpage-1, $this-
>strprevious);
}

Main Body of the Navigator

The main body of the navigator (see Listing 7-1) is created by looping through the pages, starting with the current start page.

//loop through displayed pages from $currentstart
for($x = $this->currentstartpage; $x < $this->currentendpage; $x++){
//make current page inactive if($x == $this->currentpage){
$strnavigator .= "<span class=\"$this->inactivespanname\">";
$strnavigator .= $x + 1;
$strnavigator .= "</span>\n";
}else{
$strnavigator .= $this->createLink($x, $x+1);
}
}

Listing 7-1: The main body of the navigator

This for loop creates hyperlinks for all the pages except the current page, and the number of iterations is determined by the $currentendpage data member.
As with the Move First button, the current page will be inactive, but all other pages will be hyperlinks.

Move Next and Move Last

Finally, create the Move Next and Move Last buttons in the same manner as the Move First and the Move Previous buttons, as shown in Listing 7-2.

//next button
if($this->currentpage == $this->totalpages-1){
$strnavigator .= $this->spannextinactive;
}else{
$strnavigator .= $this->createLink($this->currentpage + 1, $this->strnext);
}
//move last button
if($this->currentpage == $this->totalpages-1){
$strnavigator .= $this->lastinactivespan;
}else{
$strnavigator .= $this->createLink($this->totalpages -1, $this->strlast);
}

Listing 7-2: Creating the Move Next and Move Last buttons

Current and Total Number of Pages

The navigator proper is complete, but information about the current page and the total number of pages helps orient the user:

$strnavigator .= "</div>\n";
$strnavigator .= $this->getPageNumberDisplay();
return $strnavigator;

A terminating div tag ( ) encloses the navigator, and a call to getPageNumberDisplay creates the HTML code to display the current page and the total number of pages.

private function getPageNumberDisplay(){
$str = "<div class=\"$this->pagedisplaydivname\">\nPage ";
$str .= $this->currentpage + 1;
$str .= " of $this->totalpages";
$str .= "</div>\n";
return $str;
}

NOTE The string that displays the current page and the total number of pages is enclosed within a separate div tag in order to easily manipulate its placement and appearance.

Where to Go from Here

You’ve developed a page navigator class that implements behavior similar to the Google navigator. You’ve learned how to set the number of items shown per page and adjust the width of the navigator. The major components of the navigator have been assigned CSS class names, allowing manipulation of the navigator’s appearance. Chapter 8 will demonstrate how to use the page navigator in conjunction with the DirectoryItems class and the ThumbnailImage class, and how to configure its appearance.

0 comments: