Paging Requirements/Design

Contents

Overview

This document describes the effort to add paging to the VM module. Paging occurs when a listing (of volunteers, projects, messages, etc.) contains more rows than will fit on a page. In that case, the total number of rows needs to be divided in N pages with RPP rows per page. At the bottom of each page should be a page navigation menu consisting of links or submit buttons that allow the user to select a page by number. When a page is selected, either a GET or POST query is submitted and the new page with RPP rows is displayed.

Locations

The following locations in the vm module need paging capabilities:

  • Volunteer List
    • DAO::getVolunteers()
    • VolunteerController::controlHandler() case 'display_list_all', 'display_list_assigned'
    • VolunteerView::listVolunteers()
    • templates/volunteers/list.tpl
  • Volunteer Search
  • Volunteer Assignment
  • Project List
  • Single Project Display Page
  • Reports?

Sahana Paging Library

We make use of the Sahana paging library, inc/lib_paging.inc, which contains the following function:

_shn_page_get_rs($query,$rpp)

Given a $query and the $rpp this function will return $rpp records starting on the current page. The function calculates the LIMIT <offset>,<limit> attribute for the query based on the page parameter which is stored in the $_REQUEST[page] attribute by the previous GET or POST action.

MVC Design

Paging requires repeated database queries. Each query will retrieve RPP records. This is done in the DAO object (the model), where all DB queries are located by including the LIMIT <offset>,<limit> attribute in the query, where <offset> would be the current starting row and <limit> would be the RPP. This is a more efficient query than retrieving the entire list at once.

For the VM, paging is complicated by the fact that many queries require large joins, which is not supported by the Sahana page library. To get around this, in the case of Volunteer queries, we first query to select the p_uuid's of the volunteers, then we repeatedly query and construct Volunteer objects corresponding to those p_uuids.

Code Overview: View All Volunteers

Let's trace through the example of View All volunteers. This will lead to the following GET action:

index.php?mod=vm&act=volunteer&vm_action=display_list_all

which invokes the VolunteerController. This is the code that is executed there to display a paged result:

$this->listVolunteers($dao->getVolunteers(), $extra_opts);
$this->showPagingNavigation("index.php?mod=vm&act=volunteer&vm_action=display_list_all");

The call to $dao->getVolunteers() is where paging is handled. The following line of code retrieves a page of volunteer p_uuid's:

//get volunteer ids based on paging
$result = $this->getCurrentPage($q);

The getCurrentPage() function handles paging for all menu actions that require paging. Note that the rpp is retrieved from the $_REQUEST variable before calling the Sahana library function. The _shn_page_get_rs() function contains code within it to fetch the current page from the $_REQUEST variable. The results are passed back throught the global variable vw_page_result:

function getCurrentPage($query) {
	global $global;

	//get number of rows per page
	$rpp = isset($_REQUEST['rpp']) ? $_REQUEST['rpp'] : VM_DEFAULT_RPP;
	$result = _shn_page_get_rs($query, $rpp);

	//set the paging result and rpp to $global to use the metadata later in the views
	$global['vm_page_result'] = $result;
	$global['vm_page_rpp'] = $rpp;

	//VERY IMPORTANT: the _shn_page_get_rs() function above sets the DB query 
        // fetchmode to ADODB_FETCH_ASSOC, so to ensure proper
	//working of other things in Sahana (like location functionality), need to 
        //  reset to 3, which is both numerical and assoc
	//see http://phplens.com/adodb/reference.varibles.adodb_fetch_mode.html
	$this->db->SetFetchMode(3);

	return $result;
}

Going back to the VolunteerController, the call to showPagingNavigation() which is defined in the View class. This function calculates which paging links (or buttons) should be displayed in the view. The key code segment is here:

//Calculate starting and ending page numbers to use as links where
// we want to show 10 or 11 consecutive links plus some ellipses. The
// links themselves are figured out in the template file.
//For example, if the current page is 55 out of 200, then start
// would be 50 and end would be 60, leading eventually to a menu of links:
// 1...50 51 52 53 54 55 56 57 58 59 60...200
//If the current page is 3, this code would produce links for pages 1 through 10

$start = $page - 5;
$end = $page + 5;
$last = $res -> LastPageNo();

if($start < 1) {
    $end = $end - $start + 1;
    $start = 1;
}

if($end > $last) {
    $start = $start - ($end - $last);
    $end = $last;
    if($start < 1) {
	    $start = 1;
    }
}

These values are passed to the volunteer/paging_navigation.tpl template where they are used as follows to display the navigation links:

<?php
	// This loop calculates and displays a range of page links (or buttons) based
	//  on the starting page and ending page, e.g., 50 51 52 53 54 55 56 57 58 59 60
	// The current page link is marked in red and is inactive. All others are either
	// numbers used as links (GET) or submit buttons with page number as label (POST).
	for($i = $start; $i <= $end; $i++) {
	    if($i == $page) {
	        echo "<span class='red'>".$i."</span>";
	    } else if($i == 1 || $i == $last) {
	    	continue;
	    } else {
	    	if($use_post) {
	    		?>
	    		<input type="submit" name="page" value="<?php echo $i; ?>" />
	    		<?php
	    	} else {
				?>
				<a href="<?php echo $url?>&page=<?php echo $i?>&rpp=<?php echo $rpp;?>" title="<?php echo _('Go to Page No ').$i?>"><?php echo $i?></a>
				<?php
			}
	    }
	}
	?>

Note that this code uses either a POST or a GET. POSTs are used for search functions where the search filter parameters need to be preserved. In that case, submit buttons are displayed. Other listings use GET and display links such as '1' '2' etc.

Links